美文网首页
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