來源:http://blog.sunnyxx.com/2015/01/17/self-in-arc/
記錄下前兩天的一次討論,源于網(wǎng)絡(luò)庫YTKNetwork中“YTKRequest.m”的- start方法其中的幾行代碼:
- (void)start {
// ......
YTKRequest *strongSelf =self;
[strongSelf.delegate requestFinished:strongSelf];
if(strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
}
看起來比較有違常理,所以和猿題庫的@晨鈺Lancy,@唐巧以及網(wǎng)易的@老漢一起討論了下這個問題。
具體的問題大概是這樣:
調(diào)用方(如view controller)實例化并強引用YTKRequest對象,將自己作為其delegate
調(diào)用方調(diào)用YTKRequest的- start方法發(fā)起網(wǎng)絡(luò)請求
調(diào)用方在- requestFinished:中執(zhí)行了self.request = nil;
YTKRequest中,- start方法在回調(diào)完- requestFinished:后BAD_ACCESS了
也就是說,- start方法還未返回時,self就被外部釋放了。作者發(fā)現(xiàn)了這個潛在的問題,所以在方法局部增設(shè)了一個strongSelf的強引用來保證self的生命周期延續(xù)到方法結(jié)束。問題是解決了,但是更希望知道原因。
簡化說明就是:
- (void)foo {
// self被delegate持有
[self.delegate callout];// 外部釋放了這個對象
// 這里self野指針
}
現(xiàn)在想想還是比較不符合常理,入?yún)⒌膕elf居然不能保證這個函數(shù)執(zhí)行完成。后來查閱了下文檔,發(fā)現(xiàn)是ARC的(gao)機(de)制(gui),clang的《這篇ARC文檔》中有明確的解釋,總結(jié)如下:
ARC下,self既不是strong也不是weak,而是unsafe_unretained的,也就是說,入?yún)⒌膕elf被表示為:(init系列方法的self除外)
- (void)start {
const__unsafe_unretained YTKRequest *self;
// ...
}
在方法調(diào)用時,ARC不會對self做retain或release,生命周期全由它的調(diào)用方來保證,如果調(diào)用方?jīng)]有保證,就會出現(xiàn)上面的crash
ARC這樣做的原因是性能優(yōu)化,objc中100%的方法(不是函數(shù))調(diào)用第一個參數(shù)都是self,同時,99%的情況下,調(diào)用方都不會在方法執(zhí)行時把這個對象釋放,所以相比于在每個方法中插入對self的引用計數(shù)管理:
- (void)start {
objc_retain(self);
// 其中的代碼self一定不會被釋放
objc_release(self);
}
優(yōu)化了的性能還真是比較可觀。
而且,ARC也用了挺多方法來避免開發(fā)者進行額外的引用計數(shù)控制,比如方法的命名約定,通過判斷方法是否以如init,alloc,new,copy等關(guān)鍵字開頭來決定其內(nèi)存管理方式。
One more thing
在寫test時發(fā)現(xiàn),下面兩種調(diào)用方法會導(dǎo)致不同結(jié)果:
- (void)viewDidLoad {
// 1
[_request start];// crash
// 2
[self.request start];// 正常
}
因為self.request是一次方法調(diào)用,返回的結(jié)果被objc_retainAutoreleasedReturnValue方法在局部進行了一次強引用,關(guān)于這個方法可以看之前寫過的關(guān)于Autorelease的《這篇文章》