__bridge, __bridge_transfer, __bridge_retained

转载自微信公众号“知识小集”

 

__bridge__bridge_transfer__bridge_retained 几乎是一百年多前的知识点了,关于它们在 ARC 下的用法已有大量文章。但是这东西乍一看好简单,实际上也确实简单,但过段时间不接触再来用却难免又会犯迷糊。因此本文以更加详细的方式梳理一遍,旨在讲清楚来龙去脉,而不止是用法。

0. 一些说明

针对后面会用到的描述做一些说明。

  • CF 对象:由 CoreFoundation 创建,管理的对象,比如 CFStringRef、CFArrayRef 等,或者其他由系统 SDK 的 C 风格 API 创建,管理的对象,比如 ABAddressBookRef;

  • OC对象:就是OC对象;

  • 所有权:管理对象生命周期的权利,准确说其实是:将对象进行 Retain Count -1 的权利和义务;

  • ARC管理:对象生命周期的 retain 与 release 操作由编译器生成的代码进行管理,不需要手动管理;

  • CF手动管理:对象生命周通过手动调用 CFRetain/CFRelease 来管理。

1. __bridge

用以将 CF 对象转换为 OC 对象,或者 OC 对象转换为 CF 对象,但是不会对对象的 Retain Count、所有权产生任何影响。

CF 对象转换为 OC 对象

简单例子:

uint8_t bytes[BYTES_LENGTH] = {0x00};
CFDataRef cfData = CFDataCreate(kCFAllocatorDefault, bytes, BYTES_LENGTH);
NSData *nsData = (__bridge NSData *)cfData;

__bridge 可以理解为:只是为了让编译通过,其他毫无影响,最终还是需要手动调用 CFRelease(cfData) 来释放 cfData。

如果细心的跟一下,通过 CFGetRetainCount,会发现 cfData 赋值给 nsData 后引用计数立即 +1。注意这跟 __bridge 没有关系,而是因为 ARC 下 nsData 默认为 __strong 类型。因此在赋值给 nsData 前 ARC 生成的代码会对 cfData 进行 Retain Count +1 操作。最终通过 objc_stroreStrong(nsData, nil) 对 nsData 再进行 -1 操作。这个过程是 ARC 的本职工作范畴,跟右边的 cfData 是 CF 对象还是 OC 对象以及 __bridge 都没有关系。

更多细节可以看这两行代码对应的汇编:

OC 对象转 CF 对象

简单例子:

uint8_t bytes[BYTES_LENGTH] = {0x00};
NSData *nsData = [NSData dataWithBytes:bytes length:BYTES_LENGTH];
CFDataRef cfData = (__bridge CFDataRef)nsData;

__bridge 可以理解为:只是为了让编译通过,其他毫无影响。不需要手动调用 CFRelease(cfData) 来释放 cfData,因为对象的所有权没有改变,生命周期管理还是靠 ARC。

而且这个赋值并不会改变 nsData 的 Retain Count。和前面的情况的差别就是,这里 cfData 不属于 ARC 管理的范畴,ARC 不会为它生成管理代码。

更多细节可以看这两行代码对应的汇编:

2. __bridge_transfer

__bridge_transfer 等价于 CFBridgingRelease(),将 CF 对象转换为 OC 对象,并将所有权转移给 ARC。

所有权转移给 ARC 的本质含义是:最终 CF 对象会被 ARC 生成的代码进行 Retain Count -1 操作或者释放,所以不需要手动调用 CFRelease。

CFBridgingRelease 中的 Release 不是真的会立即进行 Release 操作,我猜这也就是为什么 CFBridgingRelease 的对应语法关键字不叫 __bridge_release,而叫 __bridge_transfer 的原因,即 transfer 所有权。

简单例子:

uint8_t bytes[BYTES_LENGTH] = {0x00};
CFDataRef cfData = CFDataCreate(kCFAllocatorDefault, bytes, BYTES_LENGTH);
NSData *nsData = (__bridge_transfer NSData *)cfData;

此时不能再调用 CFRelease(cfData) 了,否则将造成崩溃。因为通过 __bridge_transfer 已经将 cfData 所有权交给 ARC,ARC 会生成相应的代码对它进行管理。道理就好像你明确告诉我桌子上的苹果🍎归我吃了,但又自己悄悄吃了,我去吃的时候发现苹果🍎没了,就会崩溃的。

注: cfData 赋值给 nsData 这个操作不会改变 Retain Count。这里虽然 nsData 默认是 __strong 属性的,但是因为 “ARC已经被 __bridge_transfer 明确告知拥有了内存管理权”,因此编译器不会为赋值操作生成额外的 retain 代码。

感兴趣可以细看这两句代码对应汇编:

使用 __bridge_transfer 有 2 个重要原则:

1) 不属于自己的 CF 对象不要随便给 ARC,否则会造成尝试释放已释放的对象而崩溃。

比如如下代码:

CFArrayRef cfArray = [xxxxx];
NSString *value = (__bridge_transfer NSString *)CFArrayGetValueAtIndex(cfArray, 0);

这是必崩的。

根据Core Foundation内存管理的三原则:

  • If you create an object (either directly or by making a copy of another object—see The Create Rule), you own it.

  • If you get an object from somewhere else, you do not own it. If you want to prevent it being disposed of, you must add yourself as an owner (using CFRetain).

  • If you are an owner of an object, you must relinquish ownership when you have finished using it (using CFRelease).

通过 Create/Copy 方法得到的对象我们是有所有权的,但是通过 Get 得到的,是没有所有权的。

回到这个例子,Get 得到的对象,假如叫对象 Obj,这里直接将 Obj 通过 __bridge_transfer/CFBridgingRelease() 交给 ARC 管理,ARC 就会为其生成对应的释放代码,结果就是释放了不属于自己的对象 Obj。等 cfArray 真正释放的时候,也会对 Obj 进行释放操作,但其实这时候 Obj 已经被 ARC 给释放了,所以就崩了。本例如果在交给 ARC 前通过 CFRetain 得到所有权,就没毛病了:

CFArrayRef cfArray = [xxxxx];
NSString *value = (__bridge_transfer NSString *)CFRetain(CFArrayGetValueAtIndex(cfArray, 0));

2) 属于自己的 CF 对象不想管了,需移交给 ARC,否则内存泄漏。

比如如下代码:

NSString *suffix = (__bridge NSString *)ABRecordCopyValue(record, kABPersonSuffixProperty);

这里通过 Copy 方法得到了一个 CFStringRef 对象,是有所有权的。换句话说,是有显式 CFRelease 它的义务的。如果不想手动 CFRelease 它,那么可以将它交给 ARC 来管理:

NSString *suffix = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonSuffixProperty);

这时候就不会泄露了,因为 __bridge_transfer 会告诉编译器,我不想管了,你用 ARC 机制来处理他的生命周期吧。而前面第一种直接 __bridge 的结果就是,Copy 得到的对象永远得不到释放,因为 __bridge 是不对所有权产生任何影响的。或者改成如下就没毛病了:

CFStringRef cfSuffix = ABRecordCopyValue(record, kABPersonSuffixProperty);
NSString *suffix = (__bridge NSString *)cfSuffix;
CFRelease(cfSuffix);

3. __bridge_retained

__bridge_retained 等价于 CFBridgingRetain(),用以将 OC 对象转换为 CF 对象,并且 Retain Count 立即 +1

注意和 __bridge_transfer 转移所有权的差别,__bridge_retained 不存在转移所有权,而应当是赋予 CF 所有权。方式就是简单粗暴的将 Retain Count +1:编译器看到 __bridge_retained 指示符,会生成一条对 OC 对象的 retain 语句并在赋值前调用它。因此在不需要该 CF 对象的时候,必须手动调用 CFRelease 对其进行 Retain Count -1。

简单例子:

uint8_t bytes[BYTES_LENGTH] = {0x00};
NSData *nsData = [NSData dataWithBytes:bytes length:BYTES_LENGTH];
CFDataRef cfData = (__bridge_retained CFDataRef)nsData;

本例中,不需要 cfData 的时候,必须要 CFRelease(cfData),否则 cfData 得不到释放,内存泄漏。

感兴趣可以细看这两句代码对应汇编:

总结

三句话可以说清楚的事情写如此一大堆,只期望能对清楚的认识 __bridge,__bridge_transfer,__bridge_retained 有所帮助。

posted @ 2018-10-17 16:58  lxl奋小斗  阅读(254)  评论(0编辑  收藏  举报