CF对象与NS对象互转
在ARC下,如果我们需要操作一些底层的库,有时会用到Core Foundation的对象,简称CF对象,例如Core Graphic、Core Text。在ARC下,这些CF的对象的内存是不会被自动管理的,而是需要我们在它的生命周期结束的时候调用CFRelease()释放它。
CF对象与NS对象之间如何相互转换呢?系统提供了bridge,bridge_retained,__bridge_transfer 三个关键字给我们使用。
__bridge
__bridge只是单纯的对象类型的转换,并没有涉及到对象所有权的转移,所以需要把握好对象的生命周期,否则会出项野指针的情况。
1
2
3
4
5
6
7
8
9
| void *p = 0;
do {
id obj = [[UIActivity alloc] init];
p = (__bridge void *)obj; // 出了作用域,obj被释放。
} while (0);
{
id foo = [NSObject new]; // 为了切实将释放的内存被占用。
}
NSLog(@"class=%@", [(__bridge id)p class]); // p为野指针,crash
|
NS对象转为CF对象会出项野指针,逆过来CF转NS对象则有可能会出现内存泄露的问题,具体见下面的bridge_transfer的介绍。简单来说bridge就是类型强制转换。
__bridge_retained
__bridge_retained用于将NS对象转为CF对象,这其中有所有权的转移,NS对象会被retain一次再交给CF处理,这样即使原始的NS的对象在被ARC自动处理release一次之后,它的retainCount也不会为0,从而不会被销毁。
1
2
3
4
5
6
7
8
9
10
| void *p = 0;
do {
id obj = [[UIActivity alloc] init];
p = (__bridge_retained void *)obj; // 出了作用域,obj被释放。p同时也有了所有权’
} while (0);
{
id foo = [NSObject new]; // 为了切实将释放的内存被占用。
}
NSLog(@"class=%@", [(__bridge id)p class]); // 正确打印UIActivity
|
看一下引用计数的变化
1
2
3
4
| NSString *foo = [[NSString alloc] init];
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)foo)); // 1152921504606846975
CFTypeRef rfoo = (__bridge_retained CFTypeRef)foo;
NSLog(@"%lu", CFGetRetainCount(rfoo)); // 1152921504606846975
|
可以看到引用计数是一个超级大的整数,这是因为在arc下直接创建的Foundation对象的引用计数都被处理过了,无法看到具体的数值。
这一操作系统给我们提供了一个内联函数来干这件事CFBridgingRetain,
1
2
3
| NS_INLINE CF_RETURNS_RETAINED CFTypeRef __nullable CFBridgingRetain(id __nullable X) {
return (__bridge_retained CFTypeRef)X;
}
|
__bridge_transfer
__bridge_transfer用于将CF对象转为NS对象,同样的这其中也有所有权的转移,CF对象会在转换为NS对象后进行一次release操作,即把所有权完全移交给NS对象来处理,看一下引用计数的变化:
1
2
3
4
5
6
| CFStringRef ref = CFStringCreateMutable(kCFAllocatorDefault, 0);
NSLog(@"%lu", CFGetRetainCount(ref)); // 1
NSString *string = (__bridge_transfer NSString *)ref;
NSLog(@"%lu", CFGetRetainCount(ref)); // 1
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)string)); // 1
|
这里的对象是由Core Foundation创建的,所以它的引用计数可以被打印出来,可以看到在ARC环境下,string会被声明成strong类型,所以这个对象的retainCount会被加1,但是转换之后仍然为1,即CF对象已经放弃了它的所有权。
如果是__bridge的话
1
2
3
4
5
6
| CFStringRef ref = CFStringCreateMutable(kCFAllocatorDefault, 0);
NSLog(@"%lu", CFGetRetainCount(ref)); // 1
NSString *string = (__bridge NSString *)ref;
NSLog(@"%lu", CFGetRetainCount(ref)); // 2
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)string)); // 2
|
转换之后的引用计数是2,即CF和NS对象同时有着持有权,这样在出了当前的作用域后,ARC会自动给NS对象做release,但是CF对象需要手动调用CFRelease(),如果忘记了的话,那就是内存泄露。
同样,这一操作系统给也我们提供了一个内联函数来干这件事CFBridgingRetain,
1
2
3
| NS_INLINE id __nullable CFBridgingRelease(CFTypeRef CF_CONSUMED __nullable X) {
return (__bridge_transfer id)X;
}
|
总结
这其中的关系可以用下图来直接说明,记住这张图就可以了:
————————————