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指標此時已經失效了。
示例程式碼
@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
物件是在函式退出時就立即釋放,這樣導致 mo1
比 mo2
先被銷燬, mo2
訪問了無效指標導致了崩潰。
這個問題和 @synchronized
有關係,但目前我還不知道它和arc之間有什麼聯絡。下面給出另一個case,修改一行程式碼就不會崩潰了:
+ (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
外面,堆疊變成了這樣:
這時, mo2
的釋放發生在呼叫方法的結束時。
分析
使用Hooper檢視彙編程式碼,觀察 fun1
和 fun2
的不同。節選出關鍵部分:
fun1:
fun2:
核心在於: fun1
中,建立 mo2
後呼叫了 retain
, fun2
中,呼叫的則是 objc_retainAutoreleasedReturnValue
。
我們再來看看 create
方法:
關鍵的一行在最後,呼叫了 objc_autoreleaseReturnValue
。
關於 objc_retainAutoreleasedReturnValue
和 objc_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
本文作者:三豊
本文為雲棲社群原創內容,未經允許不得轉載。