我所理解的内存管理:3、Toll-Free Bridging

30、有时候你可能需要用到一些Core Foundation对象(比如CFArrayRef或者CFMutableDictionaryRef),对于这些对象,编译器是不会自动管理它们的生命周期的,你需要使用CFRetain或CFRelease之类的方法来管理它们的持有情况(ownership)。
如果要进行Core Foundation对象和Objective-C对象的相互转换,就可以使用Toll-Free Bridging。
而由于ARC已不能直接使用retain、release等方法,那么在转换的时候就需要将CF指针的持有情况告知OC指针,同理OC指针在转换成CF指针时也要告知其持有情况。

31、Toll-Free Bridging可以使用修饰符来进行转换,有3种转换方法:
(1)、__bridge
用于两个指针间的直接转换,不考虑持有情况;
(2)、__bridge_retained
用于OC指针转换成CF指针,转换之后CF指针也会持有对象。即是,转换后被赋值的指针也会持有对象。
使用CFBridgingRetain函数也有等同效果;
(3)、__bridge_transfer
用于CF指针(官方文档说的是“非OC指针”)转换成OC指针,转换之后CF指针不再持有对象。即是,转换后赋值指针不再持有对象。
使用CFBridgingRelease函数也有等同效果。

以下用4个例子来演示这3个修饰符:
32、__bridge_retained:
创建一个OC指针,通过__bridge_retained将它转换为CF指针,同时打印出retainCount:



分析一下这段代码执行过程中的持有情况:



可以证明,在使用__bridge_retained修饰符转换后CF指针也会持有对象。

33、__bridge:
如果仅仅使用__bridge做直接转换的话,会有什么问题呢?将32代码中的转换修改为使用__bridge,如下:



可以发现,在这种情况下会导致悬挂指针。所以仅仅使用__bridge做直接转换的话有时候是很危险的。

34、__bridge_transfer:
创建一个CF指针,通过__bridge_transfer将它转换为OC指针,同时打印出retainCount:



分析一下这段代码执行过程中的持有情况:



可以证明,在使用__bridge_transfer修饰符转换后CF指针不再持有对象。

35、__bridge:
同样的,试一试仅仅使用__bridge来做直接转换,看看会发生什么问题。将34代码中的转换修改为使用__bridge,并嵌套在一层花括号内限制变量的作用域,如下:



可以发现,在这种情况下会导致内存泄漏。所以在这种情况下仅仅使用__bridge做直接转换也是很危险的。

36、Toll-Free Bridging除了可以做OC指针和CF指针之间的转换,还可以做其他转换,比如上文29(4)提到的id变量和void*变量的相互转换。
虽然在ARC模式下,不允许id变量和void*变量进行直接转换,但是可以使用Toll-Free Bridging来完成这个转换。

37、在研究这种转换之前,先要了解一下void*类型的变量对它指向的对象的持有情况是否会有影响:
(1)、在MRC模式下,由于void*类型并不是NSObject的子类,所以这种类型的变量无法调用retain、retainCount等方法,也即无法影响引用计数。
所以,在MRC模式下void*类型的变量不会对它指向对象的持有情况造成任何影响;
(2)、在ARC模式下,修饰符只能用来修饰OC指针和块指针类型,而void*类型的变量作为一种无类型的变量,修饰符对这种它是不起作用的。
即是说:当定义变量id obj的时候,其实定义的是id __strong obj,而当定义void *obj的时候,定义就仅仅只是void *obj,它的作用类似于使用了__unsafe_unretained修饰符。
所以,在ARC模式下void*类型的变量也不会对它指向的对象的持有情况造成任何影响。

38、前文29(4)的代码在ARC模式下可以使用__bridge来处理如下:

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

但是通过上文已知道,仅仅使用__bridge做转换是很危险的,而且void*类型的变量不会持有它指向的对象,这也是很危险的。比如这段代码,总共有3个指针指向了这个NSObject对象,但是它的retainCount却只有2,这样就很容易造成悬挂指针了。

39、如果前两句代码使用__bridge_retained来处理这种转换,代码如下:

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

由上文已经知道:使用__bridge_retained转换后,被赋值变量也会持有这个对象。所以这段代码其实是相当于在MRC模式下的这样子转换:

id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain]; //强转为id类型后才能调用retain

这样void*类型的p变量就拥有了“持有”对象的效果。

40、如果最后一句代码使用__bridge_transfer来处理这种转换,代码如下:

id o = (__bridge_transfer id)p;

由上文已经知道:使用__bridge_transfer转换后,赋值变量不会再持有这个对象。所以这段代码其实是相当于在MRC模式下的这样子转换:

id o = (id)p;
[o retian];
[(id)p release];

这样将p变量赋值给o变量后,p变量便会有“释放”的效果了。

推荐阅读更多精彩内容