當(dāng)前位置:首頁 > 芯聞號 > 充電吧
[導(dǎo)讀]摘要:對許多開發(fā)者而言,ARC最令人失望之處莫過于蘋果公司讓ARC來管理內(nèi)存。不幸的是ARC沒有循環(huán)引用檢測器,因此很容易出現(xiàn)Retain Cycle現(xiàn)象,從而迫使開發(fā)者在編碼時要采取特殊的預(yù)防措施。

摘要:對許多開發(fā)者而言,ARC最令人失望之處莫過于蘋果公司讓ARC來管理內(nèi)存。不幸的是ARC沒有循環(huán)引用檢測器,因此很容易出現(xiàn)Retain Cycle現(xiàn)象,從而迫使開發(fā)者在編碼時要采取特殊的預(yù)防措施。

ARC中的Retain Cycle就像日本B級恐怖電影一樣。開始使用Cocoa或Cocoa Touch做開發(fā)時,你甚至不會在意它的存在。直到有一天應(yīng)用程序由于內(nèi)存泄漏而出現(xiàn)了崩潰現(xiàn)象,你才意識到它們的存在,看到像幽靈一樣的Retain Cycle無處不在。隨著歲月流逝,你學(xué)會適應(yīng)它們,發(fā)現(xiàn)它們,避免它們……但最終恐慌還在,無孔不入。

包括我在內(nèi),對于許多開發(fā)人員來說,ARC的最令人失望之處莫過于蘋果公司讓ARC來管理內(nèi)存。不幸的是ARC沒有循環(huán)引用檢測器,因此很容易出現(xiàn)Retain Cycle現(xiàn)象,從而迫使開發(fā)人員在編碼時要采取特殊的預(yù)防措施。

對于iOS開發(fā)人員來說,Retain Cycle是個難點(diǎn)。在網(wǎng)上有很多誤導(dǎo)信息[1][2],人們所給出的這些錯誤信息和修復(fù)方法甚至?xí)?dǎo)致應(yīng)用出現(xiàn)新的問題,甚至崩潰掉,基于這樣的情況,本文中我會闡明主題,給讀者一些啟發(fā)。

相關(guān)理論一瞥

Cocoa框架內(nèi)存管理可以追溯到MRR(Manual Retain Release),在MRR中,開發(fā)人員在創(chuàng)建對象的時候,要為每個內(nèi)存中的對象聲明所有權(quán)。并且,當(dāng)不再需要該對象時,要放棄所有權(quán)。MRR通過引用計(jì)數(shù)系統(tǒng)來實(shí)現(xiàn)這種所有權(quán)機(jī)制。每個對象都被分配一個計(jì)數(shù)器指示被“擁有”了多少次,每次加一,釋放對象的時候每次減一。當(dāng)引用計(jì)數(shù)變成零的時候,該對象將不復(fù)存在。對于開發(fā)人員來說,不得不手動維護(hù)引用計(jì)數(shù)真的是很煩人的事情,于是蘋果公司引入了自動引用計(jì)數(shù)(Automated Reference Counting, ARC)機(jī)制,免得開發(fā)人員手動添加保留(retain)和釋放(release)指令,讓他們專注于解決應(yīng)用程序的問題。在ARC環(huán)境下,開發(fā)人員要將一個變量定義為“strong”或“weak”。使用weak的話應(yīng)用程序中被聲明的對象不會被retain,而使用strong聲明的對象將會被retain,并且其引用計(jì)數(shù)加一。


為什么要在乎?

ARC的問題在于容易導(dǎo)致Retain Cycle,它發(fā)生在兩個不同的對象間彼此包含強(qiáng)引用的時候。試想一個Book對象包含一系列的Page對象,每個Page對象有個屬性指向該頁所在的這本書。當(dāng)你釋放掉指向Book和Page的變量時,Book和Page之間還存在著強(qiáng)引用。因此,即使沒有變量指向Book和Page了,Book和Page及其所占用的內(nèi)存也不會被釋放掉。

不幸之處在于并非所有Retain Cycle都很容易被發(fā)現(xiàn)。對象之間的傳遞關(guān)系(A引用B,B轉(zhuǎn)而引用C,C引用A)會導(dǎo)致Retain Cycle。更糟糕的是Objective-C里的塊(block)和Swift里的閉包(closure)都被認(rèn)為是獨(dú)立的內(nèi)存對象。因此,任何在塊或閉包內(nèi)對像的引用都將會對其變量做retain操作。因此,如果對象仍然retain這個塊的話,就會導(dǎo)致潛在的Retain Cycle發(fā)生。

Retain Cycle可以成為應(yīng)用程序潛在的危害,導(dǎo)致內(nèi)存消耗過高,性能低下和崩潰。但還沒有來自于蘋果公司的文檔,針對Retain Cycle可能發(fā)生的不同場景,以及如何避免進(jìn)行描述。這導(dǎo)致了一些誤解并形成了不良的編程習(xí)慣。

用例場景

那么閑話少說,我們一起來分析一些場景,確定它們是否會導(dǎo)致Retain Cycle以及如何避免:

父子對象關(guān)系

這是Retain Cycle的典型例子。不幸的是這也是蘋果公司唯一給出相關(guān)解決方案文檔的例子。就是我上面描述的Book和Page對象的例子。這種情況的典型解決方案是把Child類里面的代表父類的變量定義成weak,這樣就可以避免Retain Cycle。


[cpp]?view plaincopyclass?Parent?{?? ???var?name:?String?? ???var?child:?Child??? ???init(name:?String)?{?? ??????self.name?=?name?? ???}?? }?? class?Child?{?? ???var?name:?String?? ???weak?var?parent:?Parent!?? ???init(name:?String,?parent:?Parent)?{?? ??????self.name?=?name?? ??????self.parent?=?parent?? ???}?? }??


在Swift語言中,代表父類的變量是個弱變量的事實(shí)迫使我們將其定義為可選類型。不使用可選類型的另一種做法是將父類型對象聲明為“unowned”(意味著我們不會對變量聲明進(jìn)行內(nèi)存管理或聲明所有權(quán))。然而在這種情況下,我們必須非常仔細(xì)地確保只有一個Child實(shí)例指向Parent,Parent就不能是nil,否則程序就會崩潰:


[cpp]?view plaincopyclass?Parent?{?? ???var?name:?String?? ???var?child:?Child??? ???init(name:?String)?{?? ??????self.name?=?name?? ???}?? }?? class?Child?{?? ???var?name:?String?? ???unowned?var?parent:?Parent?? ???init(name:?String,?parent:?Parent)?{?? ??????self.name?=?name?? ??????self.parent?=?parent?? ???}?? }?? var?parent:?Parent!?=?Parent(name:?"John")?? var?child:?Child!?=?Child(name:?"Alan",?parent:?parent)?? parent?=?nil?? child.parent?<==?possible?crash?here!??


一般來說公認(rèn)的做法是父對象必須擁有(強(qiáng)引用)其子對象,這些子對象對其父對象應(yīng)該只保持一個弱引用。這同樣適用于集合,集合必須擁有其所包含的對象。

包含在實(shí)例變量中的塊和閉包

另一個經(jīng)典的例子雖然不是那么直觀,但正如我們之前所說的那樣,閉包和塊是獨(dú)立的內(nèi)存對象,并retain了它們所引用的對象。因此如果我們有一個包含閉包變量的類,這個變量又恰好引用了其所擁有對象的屬性或方法,由于閉包通過創(chuàng)建一個強(qiáng)引用而“捕獲”了自己,就會有Retain Cycle發(fā)生。


[cpp]?view plaincopyclass?MyClass?{?? ???lazy?var?myClosureVar?=?{?? ??????self.doSomething()?? ???}?? }??


這種情況下的解決方法是將自身定義成“weak”版本,并且將此弱引用賦給閉包或塊。在Objective-C語言中,要定義個新的變量:


[cpp]?view plaincopy-?(id)?init()?{?? ???__weak?MyClass?*?weakSelf?=?self;?? ???self.myClosureVar?=?^{?? ??????[weakSelf?doSomething];?? ???}?? }??


而在Swift語言中,我們只需要指定“[weak self] in”作為閉包的啟動參數(shù):


[cpp]?view plaincopyvar?myClosureVar?=?{?? ???[weak?self]?in?? ???self?.doSomething()?? }??


這樣一來,當(dāng)閉包快執(zhí)行完畢時,self變量不會被強(qiáng)制retain,因此會得到釋放,打破循環(huán)。注意,當(dāng)聲明為weak時,self在閉包內(nèi)是如何變成可選類型的。

GCD中的dispatch_async

與一貫的認(rèn)識相反,dispatch_async本身并不會導(dǎo)致Retain Cycle。


[cpp]?view plaincopydispatch_async(queue,?{?()?->?Void?in?? ???self.doSomething();??? });??


在這里,閉包對self強(qiáng)引用,但是類(self)的實(shí)例對閉包沒有任何強(qiáng)引用,因此一旦閉包結(jié)束,它將被釋放,也不會有環(huán)出現(xiàn),然而,有的時候會被(錯誤地)認(rèn)為這種情況會導(dǎo)致Retain Cycle。一些開發(fā)人員甚至一針見血地指出將塊或閉包內(nèi)所有對“self”的引用都聲明為weak:


[cpp]?view plaincopydispatch_async(queue,?{?? ???[weak?self]?in?? ???self?.doSomething()?? })??


在我看來,對每種情況都這樣做并不是好的做法。我們假設(shè)某個對象啟動了一個長時間的后臺任務(wù)(比如從網(wǎng)絡(luò)上下載一些東西),然后調(diào)用了一個“self”方法。如果對self傳遞一個弱引用的話,那么類會在閉包結(jié)束之前完成它的生命周期。因此,當(dāng)調(diào)用 doSomething()的時候,類的實(shí)例已經(jīng)不存在了,所以這個方法永遠(yuǎn)不會被執(zhí)行。這種情況下,(蘋果公司)建議的解決方案是對閉包內(nèi)的弱引用(???)聲明一個強(qiáng)引用:


[cpp]?view plaincopydispatch_async(queue,?{?? ???[weak?self]?in?? ???if?let?strongSelf?=?self?{?? ??????strongSelf.doSomething()?? ???}?? })??


我不僅發(fā)現(xiàn)語法冗長單調(diào),缺乏直觀性,甚至令人感到厭惡,而且使閉包作為獨(dú)立的處理實(shí)體的打算也落空了。我認(rèn)為要理解對象的生命周期,確切地明白什么時候應(yīng)該為實(shí)例聲明一個內(nèi)部的weak版,還要知道對象存續(xù)期間會有哪些影響。但話又說回來,這正是我解決應(yīng)用程序的問題時分散我注意力的地方,如果Cocoa框架中沒有使用ARC的話,這些都是沒有必要寫的代碼。

局部的閉包和塊

沒有引用或包含任何實(shí)例或類變量的函數(shù)局部閉包和塊本身不會導(dǎo)致Retain Cycle。常見的例子就是UIView的animateWithDuration方法:


[cpp]?view plaincopyfunc?myMethod()?{?? ???...?? ???UIView.animateWithDuration(0.5,?animations:?{?()?->?Void?in?? ??????self.someOutlet.alpha?=?1.0?? ??????self.someMethod()?? ???})?? }??


對于dispatch_async和其他GCD相關(guān)的方法,我們不用擔(dān)心沒有被類實(shí)例強(qiáng)引用的局部閉包和塊,它們不會發(fā)生Retain Cycle。

代理方案

代理(delegation)是使用弱引用避免Retain Cycle的一個典型場景。將委托聲明為weak一直是一種不錯的做法(并且還算安全)。在Objective-C中:


[cpp]?view plaincopy@property?(nonatomic,?weak)?id?

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉