Objective-C中的associated object釋放時機問題

若是對象A持有對象B,B做爲A的associated object,而且表面上B沒有其餘被強引用的地方,那麼對象A被釋放時,對象B必定會同時釋放嗎?大部分狀況下是,但真有不是的時候。最近實現代碼的時候不當心就碰到了這樣的特殊狀況。函數

需求

須要監聽對象A釋放(dealloc)並執行對象A的a方法。此時引入對象B,並做爲對象A的associated object。A釋放時觸發B釋放,在B的dealloc方法中執行A的a方法。對象B須要一個指向對象A的屬性,並聲明爲unsafe_unretained(或assign),由於weak指針此時已經失效了。oop

示例代碼

@interface MyObject1 : NSObject
@end

@implementation MyObject1
- (void)foo {
    NSLog(@"success");
}
@end

@interface MyObject2 : NSObject
@property (nonatomic, unsafe_unretained) MyObject1 *obj1;
@end

@implementation MyObject2
- (void)dealloc {
    [self.obj1 foo];
}
+ (instancetype)create {
    return [[self class] new];
}
@end

@implementation ViewController
+ (void)load {
    [self fun1];
}
+ (void)fun1 {
    MyObject1 *mo1 = [MyObject1 new];
    @synchronized (self) {
        MyObject2 *mo2 = [MyObject2 create];
        mo2.obj1 = mo1;
        objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}
@end

問題

運行時出現崩潰,unsafe_unretained指針已經野了,和預期的不同。堆棧是這樣的:優化

觀察崩潰的堆棧,發現mo2對象是被自動釋放池釋放了。由於mo1對象是在函數退出時就當即釋放,這樣致使mo1mo2先被銷燬,mo2訪問了無效指針致使了崩潰。atom

這個問題和@synchronized有關係,但目前我還不知道它和arc之間有什麼聯繫。下面給出另外一個case,修改一行代碼就不會崩潰了:spa

+ (void)fun2 {
    MyObject1 *mo1 = [MyObject1 new];
    MyObject2 *mo2 = [MyObject2 create];
    @synchronized (self) {
        mo2.obj1 = mo1;
        objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

實際上只是把mo2的聲明移動到了@synchronized外面,堆棧變成了這樣:3d

這時,mo2的釋放發生在調用方法的結束時。指針

分析

使用Hooper查看彙編代碼,觀察fun1fun2的不一樣。節選出關鍵部分:
fun1:code

fun2:對象

核心在於:fun1中,建立mo2後調用了retainfun2中,調用的則是objc_retainAutoreleasedReturnValueblog

咱們再來看看create方法:

關鍵的一行在最後,調用了objc_autoreleaseReturnValue

關於objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue,請移步 https://www.jianshu.com/p/2f05060fa377 。大意是,這兩個方法成對出現時,能夠優化掉[[obj autorelease] retain]這種騷操做。

結論

fun1中,因爲沒有objc_retainAutoreleasedReturnValue,取而代之的是retain,致使對象被放入自動釋放池。對於@synchronized爲何會形成不一樣,我尚未那麼深刻。

由於全局自動釋放池會延遲對象的釋放,若是代碼很是依賴對象的釋放時機則會比較危險。我認爲這樣作是最保險的,建立一個局部自動釋放池,保證局部變量在函數結束時當即釋放:

+ (void)fun3 {
    MyObject1 *mo1 = [MyObject1 new];
    @autoreleasepool {
        @synchronized (self) {
            MyObject2 *mo2 = [MyObject2 create];
            mo2.obj1 = mo1;
            objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
}

參考資料

objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue函數對ARC的優化 https://www.jianshu.com/p/2f05060fa377



本文做者:三豊

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索