IOS中的block和retain cycle (經(jīng)典)
retain cycle 的產(chǎn)生
說(shuō)到retain cycle,首先要提一下Objective-C的內(nèi)存管理機(jī)制。
作為C語(yǔ)言的超集,Objective-C延續(xù)了C語(yǔ)言中手動(dòng)管理內(nèi)存的方式,但是區(qū)別于C++的極其非人道的內(nèi)存管理,Objective-C提出了一些機(jī)制來(lái)減少內(nèi)存管理的難度。 比如:內(nèi)存計(jì)數(shù)。
在Objective-C中,凡是繼承自NSObject的類都提供了兩種方法,retain和release。當(dāng)我們調(diào)用一個(gè)對(duì)象的retain
時(shí),這個(gè)對(duì)象的內(nèi)存計(jì)數(shù)加1,反之,當(dāng)我們調(diào)用release時(shí),
對(duì)象的內(nèi)存計(jì)數(shù)減1,只有當(dāng)對(duì)象內(nèi)存計(jì)數(shù)為0時(shí),這個(gè)對(duì)象才真正會(huì)被釋放,此時(shí),對(duì)象的delloc方法會(huì)被調(diào)用來(lái)做些內(nèi)存回收前的工作。
內(nèi)存計(jì)數(shù)機(jī)制的好處在于我們可以明確分配一個(gè)使用權(quán)。比如,當(dāng)一個(gè)對(duì)象A要使用另外一個(gè)對(duì)象B的時(shí)候,A會(huì)retain
B一次以表示A使用B,而當(dāng)B被使用完畢之后,A會(huì)
調(diào)用B的release方法來(lái)放棄使用權(quán)。這樣,一個(gè)對(duì)象可以被多個(gè)其他對(duì)象使用。而作為使用它的對(duì)象,也不必關(guān)心自己之外
被使用對(duì)象的使用情況(內(nèi)存方面)。一般來(lái)講,對(duì)于類的成員變量,retain和release分別發(fā)生在賦值和自身釋放的時(shí)候,這就是Obj-C程序中
的經(jīng)典寫法:
頭文件中:
@property (nonatomic,retain) NSObject *obj;
在.m文件里:
- (void)dealloc{
[obj release];
[super dealloc];
}
OK,這種方式可以很容易地管理內(nèi)存,但是仍存在這一個(gè)問(wèn)題,這就是retain cycle。
Retain cycle,翻譯成中文大概叫保留環(huán)吧。既然父對(duì)象持有子對(duì)象,而子對(duì)象會(huì)隨父對(duì)象釋放而釋放,那么,如果兩個(gè)對(duì)象相互為父對(duì)象怎么辦?
比如A和B兩個(gè)對(duì)象,A持有B,B同時(shí)也持有A,按照上面的規(guī)則,A只有B釋放之后才有可能釋放,同樣B只有A釋放后才可能釋放,當(dāng)雙方都在等待對(duì)方釋放的時(shí)候, retain cycle就形成了,結(jié)果是,兩個(gè)對(duì)象都永遠(yuǎn)不會(huì)被釋放,最終內(nèi)存泄露。
retain cycle使你編程的時(shí)候不得不注意一些問(wèn)題。例如,要么盡量保持子對(duì)象引用父對(duì)象的時(shí)候使用弱引用,也就是assign,比如
@property (nonatomic,assign) NSObject *parent;
要么及時(shí)地將造成retain cycle中的一個(gè)變量設(shè)置為nil,將環(huán)break掉。如果注意點(diǎn),這并不是什么特別大的問(wèn)題。
嗯,注意點(diǎn)確實(shí)不是什么問(wèn)題,但是當(dāng)IOS 4.0只后,block的出現(xiàn),使你更需要更為謹(jǐn)慎。
block與內(nèi)存管理
block就是一段可以靈活使用的代碼,你可以把它當(dāng)變量傳遞,賦值,甚至可以把它聲明到函數(shù)體里,更靈活的是你可以在里面引用外部的環(huán)境。
最后一條使得block要有更多的考慮,既然block可以引用外部環(huán)境,那如何保證block被調(diào)用的時(shí)候當(dāng)時(shí)的環(huán)境變量不被釋放呢?(block調(diào)用
的時(shí)機(jī)可能是隨意的)
答案就是,被block引用的變量都會(huì)被自動(dòng)retain一次,這樣的話至少可以保證我們的調(diào)用是有效的。
說(shuō)到這里你能想到什么嗎?對(duì),還是retain cycle。因?yàn)閎lock中的retain是隱式的,所以極易出現(xiàn)retain cycle的問(wèn)題。
因?yàn)閎lock本身也可以看做一個(gè)對(duì)象,也存在生命周期,也可以被持有,所以當(dāng)這種情況出現(xiàn)的時(shí)候,我們?cè)撟⒁饬?,比如?/p>
DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
//...complete actions
[manager otherAction];
[manager release];
};
retain cycle 就這么形成了,即使調(diào)用了release,manager也不會(huì)釋放,因?yàn)閙anager和block相互持有了。為了解除retain cycle的話,我們可以這樣寫:
DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
//...complete actions
[manager otherAction];
manager.complete = nil;
[manager release];
};
manager的complete被設(shè)置為nil,如此一來(lái)retain cycle也被破壞掉,前提是你確實(shí)不需要再次回調(diào)block了。
本來(lái)寫到這里就算完了,但是新世紀(jì)總有新的挑戰(zhàn),這就在于在Apple有推出了一種新的技術(shù) ARC。
ARC 和 retain cycle
ARC (Auto Reference Counting),
翻譯為自動(dòng)引用計(jì)數(shù),是Apple為了進(jìn)一步簡(jiǎn)化內(nèi)存管理來(lái)推出的技術(shù)。雖然為自動(dòng)內(nèi)存管理而生,但卻并算不上真正的自動(dòng)管理。
這是因?yàn)锳RC是一種編譯期的技術(shù),它所做的是自動(dòng)識(shí)別你的代碼并轉(zhuǎn)換成retain/release的形式,在這個(gè)層面上來(lái)看,ARC無(wú)非是簡(jiǎn)化了代碼
的書寫,并提供了部分性能上的優(yōu)化, 而并不像Java之類的語(yǔ)言可以完全把垃圾回收拋之腦后(基本上)。關(guān)于ARC的細(xì)節(jié)可以看下面的網(wǎng)址:
下面我們主要談下ARC下retain cycle的問(wèn)題。
ARC中,變量可以用三個(gè)關(guān)鍵字修飾:
__strong: 賦值給這個(gè)變量的對(duì)象會(huì)自動(dòng)被retain一次,如果在block中引用它,block也會(huì)retain它一次。__unsafe_unretained: 賦值給這個(gè)變量不會(huì)被retain,也就是說(shuō)被他修飾的變量的存在不能保證持有對(duì)象的可靠性,它可能已經(jīng)被釋放了,而且留下了一個(gè)不安全的指針。不會(huì)被block retain。__week:類似于__unsafe_unretained,只是如果所持有的對(duì)象被釋放后,變量會(huì)自動(dòng)被設(shè)置為nil,這樣更安全些,不過(guò)只在IOS5.0以上的系統(tǒng)支持,同樣不會(huì)被block retain。
另外我們也可以用__block關(guān)鍵字修飾一個(gè)變量,表示這個(gè)變量能在block中被修改(值修改,而不是修改對(duì)象中的某一個(gè)屬性,可以理解為修改指針的指向)。會(huì)被自動(dòng)retain。
于其他變量不同的是被__block修飾的變量在塊中保存的是變量的地址。(其他為變量的值)
首先,上面的代碼你現(xiàn)在可以這么寫:
DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
//...complete actions
[manager otherAction];
manager.complete = nil;
};
沒(méi)什么問(wèn)題,只是去掉了ARC中禁止的release。
當(dāng)然,我們也可以這么寫。
__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
//...complete actions
[manager otherAction];
manager = nil;
};
如果不用ARC,manager不會(huì)在block中被retain,但是采用了ARC就有些復(fù)雜了。block會(huì)retain
manager變量,但是,由于__block變量保存更為底層的變量地址,
因此當(dāng)此變量被指向其他對(duì)象時(shí),block便不對(duì)原來(lái)的對(duì)象負(fù)責(zé),引發(fā)的結(jié)果就是之前對(duì)象被release掉,retain cycle被破壞。
或者這么寫:
__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];
DoSomethingManager __week *weekmanager = manager;
manager.complete = ^{
//...complete actions
[weekmanager otherAction];
};
上面的__week也可以用__unsafe_unretained替代,但是__week更安全些,雖然它不支持IOS5.0以下的系統(tǒng)。
被__week或者_(dá)_unsafe_unretained修飾的變量不會(huì)被block retain,所以不會(huì)形成retain cycle,但是小心,保證你的對(duì)象不會(huì)在complete之前被釋放,否則會(huì)得到你意向不到的結(jié)果。