C 指针和 id 的转换,理解 Toll-Free Bridge

id 和 void * 类型的相互转换

__bridge

通过 __bridge 桥接,id 和 void * 就能够相互转换。__bridge 为直接转换,不会对引用计数做特殊处理。

__bridge 转换其的安全性与赋值给 __unsafe_unretained 修饰符相近,甚至会更低。如果管理不注意赋值对象的所有者,就会因野指针而导致程序崩溃。

__bridge_retained

__bridge_retained 转换可以使要转换赋值的变量也持有所赋值的对象。

等价于 MRC 的如下代码:

1
2
3
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

看下面代码:

1
2
3
4
5
6
7
8
void *p = 0;

{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)(obj); // p 持有 obj
}

NSLog(@"class = %@", [(__bridge id)p class]);

在大括号外打印 p 的 class,结果为 NSObject,因此 obj 在大括号结束后没有被释放,p 持有了 obj。

__bridge_transfer

__bridge_transfer 提供与 __bridge_retained 相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。

1
id obj = (__bridge_transfer id)p;

等价于 MRC 的:

1
2
3
id obj = (id)p;
[obj retain];
[(id)p release];

用 Bridge 来管理 Objective-C 对象的内存

__bridge_retained 转换与 retain 类似,__bridge_transfer 转换与 release 类似。使用这两种转换,在不使用 id 类型或对象型变量时,也可以生成、持有以及释放对象。

例如可以使用 Bridge 来对 Objective-C 对象进行内存管理:

1
2
3
4
5
// 创建并持有对象
void *p = (__bridge_retained void *)[[NSObject alloc] init];

// 释放对象
(void)(__bridge_transfer id)p;

只是这种写法不会有人这样写。

Objective-C 对象和 Core Foundation 对象之间的转换

Objective-C 对象和 Core Foundation 对象之间的区别很小,不同之处只在于是由哪个框架生成的(Foundation 和 Core Foundation)。Foundation 框架生成并持有的对象可以用 Core Foundation 框架 API 释放,反之亦可。

Objective-C 对象和 Core Foundation 对象之间的转换不需要消耗额外的 CPU 资源,因此成为免费桥(Toll-Free Bridge)。

也可以调用内置函数来进行 Objective-C 对象和 Core Foundation 对象之间的转换。

1
2
3
4
5
6
7
CFTypeRef CFBridgingRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}

id CFBridgingRelease(CFTypeRef X) {
return (__bridge_transfer id)X;
}

使用函数的效果跟直接使用关键字 __bridge_retained 和 __bridge_transfer 的效果是一样的。

避免出现野指针

看下面例子:

1
2
3
4
5
6
7
8
9
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
cfObject = (__bridge CFMutableArrayRef)obj;
CFShow(cfObject);
printf("retain count = %d\n", CFGetRetainCount(cfObject));
}
printf("retain count = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);

以上代码在运行时会崩溃,因为 cfObject 在大括号内被赋值,使用 __bridge 关键字时,cfObject 是不持有 obj 的,当出大括号后,obj 被释放,cfObject 也就成野指针的,导致崩溃。

这时需要改为 __bridge_retained 或 CFBridingRetain。

1
2
3
4
5
6
7
8
9
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
cfObject = CFBridgingRetain(obj); // 改动这行代码
CFShow(cfObject);
printf("retain count = %d\n", CFGetRetainCount(cfObject));
}
printf("retain count = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);