'BAD_ACCESS' 錯誤解決記錄,一個與 '__bridge' 有關,一個與 'out parameter' 有關

一. 錯誤

Thread 1: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)

二. 錯誤代碼示例

1. __bridge

CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)([[NSAttributedString alloc] initWithString:text]);
CTLineRef line = CTLineCreateWithAttributedString(attributedString);

2. out parameter

- (void)checkZeroInArray:(NSArray <NSNumber *> *)array error:(NSError **)error {
    [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull number, NSUInteger index, BOOL * _Nonnull stop) {
        if (number.integerValue == 0) {
            if (error) { 
                *error = [NSError errorWithDomain:@"" code:100 userInfo:nil]; 
            }
            *stop = TRUE;
        }
    }];
}

...

NSError *error = nil;
[self checkZeroInArray:numbers error:&error];
if (error) {
    NSLog(@"Error: %@", error); // EXC_BAD_ACCESS錯誤
}
三. 錯誤分析

1.__bridge

You own any object you create. You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy).

    如果你創建了一個對象,而沒有指針指向它,這個對象會被立即回收,否則會造成內存泄露。這裏情況類似,CFAttributedStringRef 不受 ARC 管理,(__bridge T) op修飾符的說明中有There is no transfer of ownership, and ARC inserts no retain operations., 而[[NSAttributedString alloc] initWithString:text]受ARC管理,持有Ownship,需要負責管理內存,所以調用release方法回收了對象。

解決辦法:

CFAttributedStringRef attributedString = (__bridge_retained CFAttributedStringRef)([[NSAttributedString alloc] initWithString:text]);
CTLineRef line = CTLineCreateWithAttributedString(attributedString);

...

CFBridgingRelease(attributedString);

    讓CFAttributedStringRef own起這個對象,並由其負責釋放。所以別忘記使用完之後調用CFBridgingRelease

2.out parameter

    out parameter是一個函數向調用方返回數據的一種特殊方式(普通方式是將數據作爲函數返回值返回)。如果這個數據是個對象,並且是在函數當中創建的,就需要考慮它的內存管理問題。因爲是我創建的,我擁有Ownship,但又不能像局部變量一樣在作用域結束時 release 掉,因爲作爲返回值它要穿透邊界。所以,對於函數返回值,一般我們採用的是autorelease的方式,達到既做到 balance 又穿透邊界的目的。
    out parameter 也是這樣樣,編譯器會把函數簽名自動變成checkZeroInArray:(NSArray <NSNumber *> *)array error:(NSError *__autorelease *)error。這意味你要返回給調用方的對象是autorelease的。也就意味這這個對象會在autoreleasepool drain的時候被 release,正常情況下沒有問題,在drain之前調用方已經結束對返回值的使用,或者retain了它。 但是在這個示例裏面,enmuerateKeysAndObjectsUsingBlock 會把block放入 @autoreleasepool { <#statements#> }中執行,編譯器處理之後代碼相當於

- (void)checkZeroInArray:(NSArray <NSNumber *> *)array error:(NSError **)error {
    [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull number, NSUInteger index, BOOL * _Nonnull stop) {
       @autoreleasepool {
       	       	if (number.integerValue == 0) {
       	            if (error) { 
               	        __autorelease NSError *error = [NSError errorWithDomain:@"" code:100 userInfo:nil]; 
       	            }
               	    *stop = TRUE;
       	        }
        }
    }];
}

    意味着你新創建的 NSError 對象在循環結束的時候就被回收了,再訪問就造成了造成BAD_ACCESS錯誤。

    解決辦法:

  1. 修改簽名爲...error:(NSError *__strong *)error
  2. 在循環外面創建一個NSError的引用,用戶指向新創建的NSError,然後賦值給out parameter.

四.參考文檔

  1. https://stackoverflow.com/questions/14854521/where-and-how-to-bridge
  2. https://mp.weixin.qq.com/s/IybnvL7IOxcaVwr2GE1i3Q
  3. http://tutuge.me/2016/04/30/autoreleasing-meet-autoreleasepool/
  4. http://blog.pioneeringsoftware.co.uk/2012/03/06/out-parameters-when-arcing/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章