iOS 开发之_bridge _bridge_retained 跟 _bridge_transfer的区别

iOS 开发之__bridge __bridge_retained 和 __bridge_transfer的区别

简介:__bridge:实现id类型与void*类型的相互转换;

__bridge_retained:类型被转换时,其对象的所有权也将被变换后变量所持有

__bridge_transfer:类型被转换时,其对象的所有权也将被释放

__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release1。

详情:

在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。


这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。


比如不使用ARC时,我们用下面的代码:

NSString *string = [NSString stringWithFormat:...];

CFStringRef cfString = (CFStringRef)string;
同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。
     Use __bridge to convert directly (no change in ownership)
     Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)
错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。

CFStringRef cfString = (__bridge CFStringRef)string;
只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。
CFStringRef cfString = (__bridge_retained CFStringRef)string;
...
CFRelease(cfString); // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release


使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。
    return  (__bridge_retained  CFTypeRef)X;
}
 
id  CFBridgingRelease(CFTypeRef  X)  {
     return  (__bridge_transfer  id)X;
}


所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:
CFStringRef cfString = CFBridgingRetain(string);
...
CFRelease(cfString); // 由于Core Foundation不在ARC管理范围内,所以需要主动release。
__bridge_transfer
所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 __bridge_transfer 关键字。
NSString *string = (__bridge_transfer NSString *)cfString;
 
// CFRelease(cfString); 因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release
同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。
NSString *string = CFBridgingRelease(cfString);

总结:

由上面的学习我们了解到 ARC 中类型转换的用法,那么我们实际使用中按照怎样的原则或者方法来区分使用呢,下面我总结了几点关键要素。
Core Foundation 对象类型不在 ARC 管理范畴内
Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内
如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁
各种对象的生命周期是怎样的
1. 声明 id obj 的时候,其实是缺省的申明了一个 __strong 修饰的变量,所以编译器自动地加入了 retain 的处理,所以说 __bridge_transfer 关键字只为我们做了 release 处理。

但是在ARC有效的情况下,将出现类似下面的编译错误:

    Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast

正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。

上面的例子,使用 __bridge/__bridge_retained 后的代码如下:

NSString *string = [NSString stringWithFormat:...];

NSString *string = [NSString stringWithFormat:...];

实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。

CFTypeRef  CFBridgingRetain(id  X)  {

NSString *string = [NSString stringWithFormat:...];

CFStringRef cfString = CFStringCreate...();

CFStringRef cfString = CFStringCreate...();


明确被转换类型是否是 ARC 管理的对象