「OC」__bridge关键字的使用
前言
在学习小白书关于引用计数之中Foundation
框架和Core Foundation
框架相互转化后的相关内容,学习到了__bridge
这个关键字,因此对其进行学习
引入
在cocoa
应用中,我们离不开Foundation
同时又经常用到Core Foundation
,Foundation
是Core Foundation
的一层包装,其底层数据结构是一样的,我们可以使用__bridge
在CF
对象和NS
对象之间相互转化
,但是不移交所有权
。
以下是书中给出的例子
NSString *str = @"abc";
CFStringRef cfstring = (__bridge CFStringRef)str;
NSLog(@"1 -- %@",cfstring);
NSString *s = (__bridge NSString *)cfstring;
NSLog(@"2 -- %@",s);
执行结果
1 -- abc
2 -- abc
因为Core Foundation
对象与Objective-C
对象没有区别,所以在ARC无效时,只用简单的C语言的转换也能实现互换。另外这种转换不需要使用额外的CPU 资源,因此也被称 为 “ 免费桥 " ( Toll-FreeBridge)。
CFTypeRef CBridgingRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}
id CBridgingRelease(CFTypeRef X) {
return (__bridge_transfer id)X;
}
以上就是使用免费桥进行转换的程序,我们借机来深入了解一下__bridge_retained
和__bridge_transfer
__bridge_retained:
- 这条语句
(__bridge_retained CFTypeRef)X
告诉编译器把一个 ARC 管理的 Objective-C 对象转换为 Core Foundation 对象,同时转移所有权(即对象的引用计数不会减少,调用者需要负责释放)。 - 这通常用于将对象传递给需要 Core Foundation 类型的 API,并且明确表示调用者接管了对象的释放责任。
__bridge_retained
或CFBridgingRetain
将OC
指针转为CF
指针并且移交所有权
,需要CF
通过CFRelease
来负责对象的释放,以书中的例子:
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
// 此时,变量 obj 持有生成对象的强引用,引用计数为1
// CFBridgingRetain 会把 ARC 管理的对象转换成 Core Foundation 对象,并且转移所有权(即对对象执行 CFRetain 操作),引用计数加1
cfObject = CFBridgingRetain(obj); // 或写作: cfObject = (__bridge_retained CFMutableArrayRef)obj;
// 现在,对象有两个所有者:obj 的强引用和 cfObject 的所有权引用,所以引用计数为2
CFShow(cfObject);
printf("retain count = %d\n", (int)CFGetRetainCount(cfObject));
}
// 当 obj 超出其作用域后,ARC 自动释放 obj 持有的引用,对象引用计数减1,仍为1
printf("retain count after the scope = %d\n", (int)CFGetRetainCount(cfObject));
// 通过 CFRelease 手动释放 cfObject 的引用,对象引用计数减为0,对象被销毁
CFRelease(cfObject);
使用__bridge
关键词直接代替
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
// 此时,变量 obj 持有生成对象的强引用,引用计数为1
// 使用 __bridge 进行转换,仅仅是类型转换,不改变引用计数,也不转移所有权
cfObject = (__bridge CFMutableArrayRef)obj;
// 此时,对象只有一个所有者,即 obj,引用计数依然为1
CFShow(cfObject);
printf("retain count = %d\n", (int)CFGetRetainCount(cfObject));
}
// 当 obj 超出作用域后,ARC 会释放 obj 持有的对象,对象引用计数降为0,对象被释放
// 但 cfObject 仍保存着原始指针,此时 cfObject 成为悬垂指针
printf("retain count after the scope = %d\n", (int)CFGetRetainCount(cfObject));
// 如果继续使用 cfObject 或调用 CFRelease,可能会出现错误,因为对象已经被释放
// CFRelease(cfObject); // 切勿在 __bridge 转换后调用 CFRelease
__bridge_transfer:
- 而
(__bridge_transfer id)X
则是将一个 Core Foundation 对象转换为 ARC 管理的 Objective-C 对象,同时将所有权转交给 ARC。 - 这样 ARC 会负责对象的释放,无需手动调用 CFRelease。
和__bridge_retained
相反,是将CF
指针转为OC
指针并移交所有权
{
CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
printf("retain count = %d\n", CFGetRetainCount(cfObject)); // 创建后引用计数为1
// 通过 CFBridgingRelease 将 CF 对象转为 ARC 管理的 Objective-C 对象
id obj = CFBridgingRelease(cfObject);
// 此时,CF 对象的所有权已转移给 ARC,ARC 会对 obj 进行一次强引用(retain)管理
printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject)); // 引用计数仍为1
NSLog(@"class = %@", obj);
}
// 当变量 obj 超出作用域,其强引用失效,对象会被自动释放(CFRelease 在内部调用)
使用__bridge
关键词直接代替
{
CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
printf("retain count = %d\n", CFGetRetainCount(cfObject)); // 创建后引用计数为1
// 使用 __bridge 转换,将 CF 对象赋值给 ARC 管理的 Objective-C 对象
id obj = (__bridge id)cfObject;
// 由于赋值给强引用的变量,ARC 会额外对对象进行一次 retain,引用计数变为2
printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject)); // 引用计数为2
NSLog(@"class = %@", obj);
}
// 当变量 obj 超出作用域,ARC 会自动释放其持有的引用,但 CF 对象仍有一份原始引用
// 这会导致对象未被销毁,发生内存泄漏!
慎用
尽管以上方法,可以实现C语言框架和OC框架的互换,但是我们这些方法尽可能还是少用,因为对于CF框架的内容来说,很容易忘记作用域相关的问题
void *p;
{
Son *son = [[Son alloc] init];
// 将OC指针转为CF指针赋值给p
p = (__bridge void *)son;
// 出了作用域OC对象就要释放了
}
// 这里访问空对象会出问题
NSLog(@"%@",p);
我们需要使用的是__bridge_retained
void *p;
{
Son *son = [[Son alloc] init];
// 将OC指针转为CF指针赋值给p,并将所有权交给CF管理
p = (__bridge_retained void *)son;
// 出了作用域并不会影响p对象
}
// 这里仍然可以正常访问
NSLog(@"%@",p);
执行结果
<Son: 0x281210150>