原文内容
在Objective-C Automatic Reference Counting (ARC)中的Bridged casts的内容如下:
A bridged cast is a C-style cast annotated with one of three keywords:
-
(__bridge T) op
casts the operand to the destination typeT
. IfT
is a retainable object pointer type, thenop
must have a non-retainable pointer type. IfT
is a non-retainable pointer type, thenop
must have a retainable object pointer type. Otherwise the cast is ill-formed. There is no transfer of ownership, and ARC inserts no retain operations. -
(__bridge_retained T) op
casts the operand, which must have retainable object pointer type, to the destination type, which must be a non-retainable pointer type. ARC retains the value, subject to the usual optimizations on local values, and the recipient is responsible for balancing that +1. -
(__bridge_transfer T) op
casts the operand, which must have non-retainable pointer type, to the destination type, which must be a retainable object pointer type. ARC will release the value at the end of the enclosing full-expression, subject to the usual optimizations on local values.
These casts are required in order to transfer objects in and out of ARC control; see the rationale in the section on conversion of retainable object pointers.
Using a __bridge_retained
or __bridge_transfer
cast purely to convince ARC to emit an unbalanced retain or release, respectively, is poor form.
大概的意思如下:
桥接强制转换是C风格的强制转换使用如下三种关键词其中之一来注解:
-
(__bridge T) op
把操作数强制转换到目标类型T
。如果T
是一个可保持对象指针类型,那么op
必须是一个非可保持指针类型。如果T
是一个非保持指针类型,那么op
必须是一个可保持对象指针类型。否则这种强制转换是不规范的。那不会转换从属关系,ARC不会插入保持操作。 -
(__bridge_retained T) op
强制转换操作数,这个操作数必须是可保持对象指针类型,目标类型必须是一个非可保持指针类型。ARC会保持这个值,它是从属于本地变量有用的优化,接收者有责任使用+1来平衡。 -
(__bridge_transfer T) op
强制转换操作数,这个操作数必须是非可保持指针类型,目标类型必须是一个可保持对象指针类型。ARC将会在整个表达式结束的时候释放此值,它是从属于本地变量有用的优化。
这种强制转换是必要的,为了转换对象能进入或者脱离ARC的控制;在conversion of retainable object pointers可保持对象指针的转换章节查看相关的理论基础。
使用__bridge_retained
或者__bridge_transfer
强制转换纯粹的确信ARC发出不公正的保持和释放,各自的,是不良形式。
那我们就开始重点测试上述的三条信息。
RXMRCUtil
在测试前,因为需要加一个得到引用计数的函数
@implementation RXMRCUtil
+ (NSUInteger)objectRetainCount:(id)object
{
return [object retainCount];
}
@end
可保持对象指针类型retainable object pointer type
和不可保持对象指针类型non-retainable pointer type
retainable object pointer type
: NSString*, NSObject * 等等
non-retainable pointer type
: CFStringRef 等等
不可保持对象指针类型在ARC下过度释放问题
- (void)_test_over_release
{
CFStringRef stringRef2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "123", kCFStringEncodingUTF8);
CFRelease(stringRef2);
// 这种字符串可以过度释放,不会崩溃
CFRelease(stringRef2);
CFStringRef stringRef = CFStringCreateWithCString(CFAllocatorGetDefault(), "123-%d-abc", kCFStringEncodingUTF8);
CFRelease(stringRef);
// 这种字符串不可以过度释放,否则会崩溃
CFRelease(stringRef);
}
出现这种应该是跟类簇有关,因为我们要测试对象的引用计数,申请,释放问题,因此我们会使用这个(内容是:"123-%d-abc"):
CFStringRef stringRef = CFStringCreateWithCString(CFAllocatorGetDefault(), "123-%d-abc", kCFStringEncodingUTF8);
__bridge T 把不可保持对象指针类型强制转换为可保持对象指针类型
- - (void)_test_bridge_T_from_NonRetainablePointerType_to_RetainablePointerType
{
CFStringRef stringRef = CFStringCreateWithCString(CFAllocatorGetDefault(), "123-%d-abc", kCFStringEncodingUTF8);
NSString *string = (__bridge NSString *)stringRef;
NSLog(@"before string release:%@, count1:%zd", string, [RXMRCUtil objectRetainCount:string]);
CFRelease(stringRef); // 第一个CFRelease
NSLog(@"after string release:%@, count2:%zd", string, [RXMRCUtil objectRetainCount:string]);
// 在这里不会崩溃,相当于过渡释放了
CFRelease(stringRef); // 第二个CFRelease
// 这里([RXMRCUtil objectRetainCount:string])会崩溃,此时string是野指针了
NSLog(@"after string release twice:%@, count3:%zd", string, [RXMRCUtil objectRetainCount:string]);
} // 如果注释count3,在这里会崩溃, 在这种转换下因为ARC最后会添加 [string release]
输出:
before string release:123-%d-abc, count1:2
after string release:123-%d-abc, count2:1
根据注释和输出结果可以得到如下结果
原来的那个非可保持对象还是需要通过
CFRelese
去释放,ARC会添加一个retain
(通过输出结果count1:2,可以调用两次CFRelease(CFRelease不崩溃)),并且在函数的结尾处添加一个release
(注释掉count3后,崩溃的位置是在方法的}
).
所以得到的结论跟原来好像有点不一样(也许是跟文档比较旧有关):
使用
__bridge T
把不可保持对象指针类型强制转换为可保持对象指针类型的时候,是不转换对象所有权,但是在这种情况下,这个对象同时加入了ARC的管理了,ARC自动加上了相关的retain
和release
操作了。
正确的使用方式是:
一定要加第一个
CFRelease
,去掉第二个CFRelease
,如果不加CFRelease
会导致内存泄漏
__bridge T 把可保持对象指针类型强制转换为不可保持对象指针类型
- (void)_test_bridge_T_from_RetainablePointerType_to_NonRetainablePointerType
{
NSString *string = [[NSString alloc] initWithFormat:@"123-%zd-abc", 456];
NSLog(@"before string release:%@, count1:%zd", string, [RXMRCUtil objectRetainCount:string]);
CFStringRef stringRef = (__bridge CFStringRef)string;
NSLog(@"before string release:%@, count2:%zd", string, [RXMRCUtil objectRetainCount:string]);
// 这里不会崩溃
CFRelease(stringRef);
// 这里([RXMRCUtil objectRetainCount:string])会崩溃,此时string是野指针了
// NSLog(@"after string release:%@, count3:%zd", string, [RXMRCUtil objectRetainCount:string]);
} // 这里会崩溃, arc 加了 [string release],因为这个string是alloc init的
输出结果:
after string init:123-456-abc, count1:1
before string release:123-456-abc, count2:1
根据输出结果和注释中崩溃的位置我们可得知:
string
没有脱离ARC的控制,不可保持对象指针类型没有获得所有权不需要CFRelease
这个跟文档中描述的是一致的。
正确的使用方法是:
去掉
CFRelease
,不要加任何的CFRelease
__bridge retained 只能从可保持对象强制转换为不可保持对象
- (void)_test_bridge_retained_from_RetainablePointerType_to_NonRetainablePointerType
{
NSString *string = [[NSString alloc] initWithFormat:@"123-%zd-abc", 456];
NSLog(@"before string release:%@, count1:%zd", string, [RXMRCUtil objectRetainCount:string]);
// 这一行是不是让string脱离ARC的控制,是让stringRef强引用!
CFStringRef stringRef = (__bridge_retained CFStringRef)string;
NSLog(@"before string release:%@, count2:%zd", string, [RXMRCUtil objectRetainCount:string]);
// 这里不会崩溃
CFRelease(stringRef);
NSLog(@"after string release:%@, count3:%zd", string, [RXMRCUtil objectRetainCount:string]);
} // 这里也不会崩溃
输出结果:
before string release:123-456-abc, count1:1
before string release:123-456-abc, count2:2
after string release:123-456-abc, count3:1
从注释和输出结果来看
不但在ARC的管理下,而且在不可保持对象的管理下,因为引用计数的数值变化了
在YYLabel
中有如下的这段代码:
- (void)_clearContents {
CGImageRef image = (__bridge_retained CGImageRef)(self.layer.contents);
self.layer.contents = nil;
if (image) {
dispatch_async(YYLabelGetReleaseQueue(), ^{
CFRelease(image);
});
}
}
也许就能明白他这么用的原理了。
毕竟也许我们一般会这样写:
- (void)_clearContents2 {
id image = self.layer.contents;
self.layer.contents = nil;
if (image) {
dispatch_async(YYLabelGetReleaseQueue(), ^{
[image class];
});
}
}
上述的两种写法是等价的(equivalent)。
这个描述是没有问题的。
__bridge transfer 只能从不可保持对象强制转换成可保持对象
- (void)_test_bridge_transfer_from_NonRetainablePointerType_to_RetainablePointerType
{
CFStringRef stringRef = CFStringCreateWithCString(CFAllocatorGetDefault(), "123-%d-abc", kCFStringEncodingUTF8);
// 把stringRef加入到ARC的控制中
NSString *string = (__bridge_transfer NSString *)stringRef;
NSLog(@"before string release:%@, count1:%zd", string, [RXMRCUtil objectRetainCount:string]);
CFRelease(stringRef);
// 这里崩溃了([RXMRCUtil objectRetainCount:string]),string已经被提前释放了
NSLog(@"after string release:%@, count2:%zd", string, [RXMRCUtil objectRetainCount:string]);
} // 如果count3被注释,这里崩溃了,因为加了[string release],因为__bridge_transfer缘故
输出结果:
before string release:123-%d-abc, count1:1
从上述注释和结果可得知:
对象会脱离不可保持对象的管理,而自动进入ARC的管理,它的所有权会丢失
正确的使用方法是:
不能有CFRelease
总结
从上面的所有测试结果来说:
Type | From | To | ARC管理变化 | CF的管理变化 |
---|---|---|---|---|
__bridge T | Non-Retainable | Retainable | 进入管理 | 不会丢失管理 |
__bridge T | Retainable | Non-Retainable | 不会丢失管理 | 不会获得管理 |
__bridge retained | Retainable | Non-Retainable | 不会丢失管理 | 会获得管理(需要使用CFRelease ) |
__bridge transfer | Non-Retainable | Retainable | 进入管理 | 丢失管理(不能使用CFRelease ) |
在当前的clang
版本中,对象并不会脱离ARC的管理,只有可能是进入ARC的管理
网友评论