__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 有所帮助。