retain cycle(保留環(huán))

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)址:

http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

下面我們主要談下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é)果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容