美文网首页
ARC下 bridged cast探究

ARC下 bridged cast探究

作者: 传说中的汽水枪 | 来源:发表于2019-01-23 15:48 被阅读10次

原文内容

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 type T. If T is a retainable object pointer type, then op must have a non-retainable pointer type. If T is a non-retainable pointer type, then op 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自动加上了相关的retainrelease操作了。

正确的使用方式是:

一定要加第一个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的管理

相关文章

网友评论

      本文标题:ARC下 bridged cast探究

      本文链接:https://www.haomeiwen.com/subject/ufdjjqtx.html