ARC下 bridged cast探究

2019-01-23  本文已影响10人  传说中的汽水枪

原文内容

Objective-C Automatic Reference Counting (ARC)中的Bridged casts的内容如下:

A bridged cast is a C-style cast annotated with one of three keywords:

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风格的强制转换使用如下三种关键词其中之一来注解:

这种强制转换是必要的,为了转换对象能进入或者脱离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的管理

上一篇 下一篇

猜你喜欢

热点阅读