《Objective-C基礎(chǔ)教程》讀書(shū)筆記6—內(nèi)存管理

內(nèi)存管理
內(nèi)存管理是程序設(shè)計(jì)中常見(jiàn)的資源管理的一部分。
雖然說(shuō)當(dāng)程序運(yùn)行結(jié)束時(shí),操作系統(tǒng)將收回其占用的資源,但是只要程序還在運(yùn)行,它就會(huì)一直占用資源。如果不進(jìn)行清理,某些資源最終將被耗盡,程序有可能會(huì)崩潰。而且隨著操作系統(tǒng)的發(fā)展,程序何時(shí)終止運(yùn)行會(huì)變得更加難以捉摸。
每個(gè)程序都會(huì)消耗內(nèi)存。
使用腳本語(yǔ)言的程序員則不需要考慮此類(lèi)問(wèn)題:這些語(yǔ)言的內(nèi)存管理是自動(dòng)進(jìn)行的。
對(duì)于內(nèi)存管理,我們需要注意兩點(diǎn):
1.我們必須確保在需要的時(shí)候分配內(nèi)存,在程序運(yùn)行結(jié)束時(shí)釋放占用的內(nèi)存。如果我們只分配而不釋放內(nèi)存,則會(huì)發(fā)生內(nèi)存泄漏(leak memory): 程序的內(nèi)存占用量不斷增加,最終會(huì)被耗盡并導(dǎo)致程序崩潰。
2.不要使用任何剛釋放的內(nèi)存,否則可能誤用陳舊的數(shù)據(jù),從而引發(fā)各種各樣的錯(cuò)誤,而且如果該內(nèi)存已經(jīng)加載了其他數(shù)據(jù),將會(huì)破壞這些新數(shù)據(jù)。
1.1對(duì)象生命周期
對(duì)象的生命周期包括誕生(通過(guò)alloc或new方法實(shí)現(xiàn))、生存(接收消息并執(zhí)行操作)、交友(通過(guò)復(fù)合以及向方法傳遞參數(shù))以及最終死去(被釋放掉)。當(dāng)生命周期結(jié)束時(shí),它們的原材料(內(nèi)存)將被回收以供新的對(duì)象使用。
1.1.1引用計(jì)數(shù)
Cocoa采用了一種引用計(jì)數(shù)(reference counting)的技術(shù),有時(shí)也叫做保留技術(shù)(retain counting)。每個(gè)對(duì)象都有一個(gè)與之相關(guān)聯(lián)的整數(shù),被稱作它的引用計(jì)數(shù)器或保留技術(shù)器。
當(dāng)使用alloc、new方法或者通過(guò)copy消息(接收到消息的對(duì)象會(huì)創(chuàng)建一個(gè)自身的副本)創(chuàng)建一個(gè)對(duì)象時(shí),對(duì)象的保留計(jì)數(shù)器值被設(shè)置為1。要增加對(duì)象的保留計(jì)數(shù)器的值,可以給對(duì)象發(fā)送一條retain消息。要減少的話,可以給對(duì)象發(fā)送一條release消息。
當(dāng)一個(gè)對(duì)象因其保留計(jì)數(shù)器歸0而即將被銷(xiāo)毀時(shí),Objective-C會(huì)自動(dòng)向?qū)ο蟀l(fā)送一條dealloc消息。你可以在自己的對(duì)象中重寫(xiě)dealloc方法,這樣就能釋放掉已經(jīng)分配的全部相關(guān)資源。一定不要直接調(diào)用dealloc方法,Objective-C會(huì)在需要銷(xiāo)毀對(duì)象時(shí)自動(dòng)調(diào)用它。
如果對(duì)一個(gè)對(duì)象使用了alloc、new或copy操作,釋放該對(duì)象(向該對(duì)象發(fā)送release消息)就能銷(xiāo)毀它并能收回它所占用的內(nèi)存。
1.1.2對(duì)象所有權(quán)(object ownership)
當(dāng)我們說(shuō)某個(gè)實(shí)體“擁有一個(gè)對(duì)象”時(shí),就意味著該實(shí)體要負(fù)責(zé)確保對(duì)其所擁有的對(duì)象進(jìn)行清理。如果一個(gè)對(duì)象內(nèi)有指向其他對(duì)象的實(shí)例變量,則稱該對(duì)象擁有這些對(duì)象。
當(dāng)多個(gè)實(shí)體擁有某個(gè)特定對(duì)象時(shí),對(duì)象的所有權(quán)關(guān)系就更加復(fù)雜了,這也是保留計(jì)數(shù)器的值大于1的原因。也許也正是內(nèi)存管理這一模塊很復(fù)雜的原因。
對(duì)象所有權(quán)最為本質(zhì)的問(wèn)題: 就是誰(shuí)負(fù)責(zé)確保對(duì)象不再被使用時(shí)能夠收到release消息。
1.1.3 訪問(wèn)方法中的保留和釋放
最合理的setter方法
-(void)setEngine:(Engine *)newEngine{
[newEngine retain];
[engine release];
engine = newEngine;
}
上述寫(xiě)法的思想:先保留新對(duì)象,再釋放舊對(duì)象。這種寫(xiě)法避免了兩個(gè)問(wèn)題的發(fā)生:
第一個(gè)問(wèn)題:舊對(duì)象的內(nèi)存泄漏問(wèn)題
第二個(gè)問(wèn)題:新對(duì)象與舊對(duì)象如果是同一個(gè)對(duì)象,引發(fā)的野指針問(wèn)題。
1.1.4自動(dòng)釋放
比較復(fù)雜的問(wèn)題:在某些情況下弄清楚什么時(shí)候不再使用一個(gè)對(duì)象并不容易。
最常見(jiàn)的是description方法。調(diào)用description方法的代碼將返回的字符串存儲(chǔ)在某個(gè)變量中,并在使用完畢后將其釋放。思想:使用完畢后再釋放。
1.1.5所有對(duì)象放入池中
自動(dòng)釋放池(autorelease pool): 是一個(gè)用來(lái)存放對(duì)象的池子(集合),并且能夠自動(dòng)釋放。
NSObject類(lèi)提供了一個(gè)叫做autorelease的方法:
-(id)autorelease;
該方法預(yù)先設(shè)定了一條會(huì)在未來(lái)某個(gè)時(shí)間發(fā)送的release消息,其返回值是接收這條消息的對(duì)象。這一特性與retain消息采用了相同的技術(shù),使嵌套調(diào)用更加容易。當(dāng)給一個(gè)對(duì)象發(fā)送autorelease消息時(shí),實(shí)際上將該對(duì)象添加到了自動(dòng)釋放池中。當(dāng)自動(dòng)釋放池被銷(xiāo)毀時(shí),會(huì)向該池中的所有對(duì)象發(fā)送release消息。
1.1.6自動(dòng)釋放池的銷(xiāo)毀時(shí)間
有兩種方法可以創(chuàng)建一個(gè)自動(dòng)釋放池。
1.通過(guò)@autoreleasepool關(guān)鍵字
2.通過(guò)NSAutoreleasePool對(duì)象
在我們一直使用的Foundation庫(kù)工具集中,創(chuàng)建和銷(xiāo)毀自動(dòng)釋放池已經(jīng)由@autorelease關(guān)鍵字完成。當(dāng)你使用@autorelease{}時(shí),所有在花括號(hào)里的代碼都會(huì)被放入這個(gè)新池子里。如果你的程序運(yùn)算是內(nèi)存密集型的,你可以使用這種自動(dòng)釋放池。有一點(diǎn)需要記住,任何在花括號(hào)里定義的變量在括號(hào)外就無(wú)法使用了。類(lèi)似于C語(yǔ)言中的有效范圍。
第二種更加明確的方法就是使用NSAutoreleasePool對(duì)象。如果你使用了這個(gè)方法,創(chuàng)建和釋放NSAutoreleasePool對(duì)象之間的代碼就會(huì)使用這個(gè)新的池子。創(chuàng)建了一個(gè)自動(dòng)釋放池后,該池就會(huì)自動(dòng)成為活動(dòng)的池子。釋放該池后,其保留計(jì)數(shù)器的值歸0,然后該池被銷(xiāo)毀。在銷(xiāo)毀的過(guò)程中,該池將釋放其所包含的所有對(duì)象。
推薦使用關(guān)鍵字方法。它比對(duì)象方法更快,因?yàn)镺C語(yǔ)言創(chuàng)建和釋放內(nèi)存的能力遠(yuǎn)在我們之上。
使用Appkit時(shí),Cocoa定期自動(dòng)地為你創(chuàng)建和銷(xiāo)毀自動(dòng)釋放池。通常是在程序處理完當(dāng)前事件(如鼠標(biāo)單擊或鍵盤(pán)按下)以后執(zhí)行這些操作。你可以使用任意數(shù)目的自動(dòng)釋放池對(duì)象,當(dāng)不再使用它們時(shí),自動(dòng)釋放池將自動(dòng)為你清理這些對(duì)象。
你可能在Xcode的自動(dòng)生成代碼中見(jiàn)過(guò)另一種銷(xiāo)毀自動(dòng)釋放池中對(duì)象的方式:-drain方法。該方法只是清空自動(dòng)釋放池而不會(huì)銷(xiāo)毀它,而且只適用于Mac OSX 10.4(Tiger)以后的操作系統(tǒng)版本。在自己編寫(xiě)(而不是由Xcode生成)的代碼中,我們使用-release方法,因?yàn)樵摲椒梢灾С指爬系腛SX版本。
1.1.7 自動(dòng)釋放池的工作流程
當(dāng)對(duì)象接收到一條autorelease消息時(shí),其保留計(jì)數(shù)器的值并不會(huì)發(fā)生改變。該對(duì)象只是被放入了NSAutoreleasePool當(dāng)中。(NSAutoreleasePool是一個(gè)普通對(duì)象,與其他對(duì)象一樣要遵從相同的內(nèi)存管理規(guī)則。) 當(dāng)自動(dòng)釋放池被銷(xiāo)毀時(shí),會(huì)向池中所有的對(duì)象發(fā)送一條release消息,所有被自動(dòng)釋放的對(duì)象都將其保留計(jì)數(shù)器的值減1。如果保留計(jì)數(shù)器的值歸0了,則對(duì)象被銷(xiāo)毀。深入理解就是:如果對(duì)象的引用計(jì)數(shù)不為0,則該對(duì)象依然存在,所以才會(huì)造成內(nèi)存泄漏。(該釋放的對(duì)象未被釋放) 使用@autoreleasepool也能達(dá)到同樣的目的,不過(guò)它并不需要分配或銷(xiāo)毀自動(dòng)釋放池對(duì)象。
在使用AppKit或UIKit的時(shí)候,自動(dòng)釋放池會(huì)在明確的時(shí)間創(chuàng)建或釋放,比如在處理當(dāng)前用戶事件的時(shí)候。除此以外,你要?jiǎng)?chuàng)建自己的自動(dòng)釋放池。Foundation庫(kù)工具集的模板中包含了這些代碼。
1.2Cocoa的內(nèi)存管理規(guī)則
我們已經(jīng)學(xué)習(xí)了各種方法:retain、release和autorelease。Cocoa有許多內(nèi)存管理約定,它們都是一些很簡(jiǎn)單的規(guī)則,可應(yīng)用于整個(gè)工具集內(nèi)。
這些規(guī)則如下:
1.當(dāng)你使用new、alloc或copy方法創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的保留計(jì)數(shù)器的值為1。當(dāng)不再使用該對(duì)象時(shí),你應(yīng)該向該對(duì)象發(fā)送一條release或autorelease消息。這樣,該對(duì)象將在其使用壽命結(jié)束時(shí)被銷(xiāo)毀。
2.當(dāng)你通過(guò)其他方法獲得一個(gè)對(duì)象時(shí),假設(shè)該對(duì)象的保留計(jì)數(shù)器的值為1,而且已經(jīng)被設(shè)置為自動(dòng)釋放,那么你不需要執(zhí)行任何操作來(lái)確保該對(duì)象得到清理。如果你打算在一段時(shí)間內(nèi)擁有該對(duì)象,則需要保留它并確保在操作完成時(shí)釋放它。
3.你保留了某個(gè)對(duì)象,就需要(最終)釋放或自動(dòng)釋放該對(duì)象。必須保持retain方法和release方法的使用次數(shù)相等。
如果你使用了new、alloc或copy方法獲得一個(gè)對(duì)象,就釋放或自動(dòng)釋放該對(duì)象。只要記住這條規(guī)則,就不用擔(dān)心內(nèi)存釋放的問(wèn)題了。
無(wú)論什么時(shí)候擁有一個(gè)對(duì)象,有兩件事必須弄清楚:怎樣獲得該對(duì)象的?打算擁有多長(zhǎng)時(shí)間?
1.2.1臨時(shí)對(duì)象
第一個(gè)例子:例如可變數(shù)組,如果你是用new、alloc或copy方法獲得的這個(gè)對(duì)象,就需要安排好該對(duì)象的內(nèi)存釋放,通常使用release消息來(lái)實(shí)現(xiàn)。如果你使用arrayWithCapacity:方法,則不需要關(guān)心如何銷(xiāo)毀該對(duì)象。arrayWithCapacity:方法與alloc、new、copy這三個(gè)方法不同,因此可以假設(shè)該對(duì)象被返回時(shí)保留計(jì)數(shù)器的值為1且已經(jīng)被設(shè)置為自動(dòng)釋放。當(dāng)自動(dòng)釋放池被銷(xiāo)毀時(shí),向array對(duì)象發(fā)送release消息,該對(duì)象的保留計(jì)數(shù)器的值歸0,其占用的內(nèi)存被回收。
第二個(gè)例子:blueColor方法也不屬于alloc、new、copy這三個(gè)方法,因此可以假設(shè)該對(duì)象的保留計(jì)數(shù)器的值為1并且已經(jīng)被設(shè)置為自動(dòng)釋放。
全局單例(singleton)對(duì)象——每個(gè)需要訪問(wèn)它的程序都可以共享的單一對(duì)象,這個(gè)對(duì)象永遠(yuǎn)不會(huì)被銷(xiāo)毀。
1.2.2擁有對(duì)象
如果你使用了new、alloc或copy方法獲得了一個(gè)對(duì)象,則不需要執(zhí)行任何其他操作。該對(duì)象的保留計(jì)數(shù)器的值為1,因此它將一直存在著,你只需要確保在擁有該對(duì)象的dealloc方法中釋放它即可。
如果你使用除alloc、new或copy以外的方法獲得了一個(gè)對(duì)象,需要記得保留該對(duì)象。
如果你編寫(xiě)的是GUI程序,要考慮到事件循環(huán)。你需要保留自動(dòng)釋放的對(duì)象,以便這些對(duì)象在當(dāng)前的事件循環(huán)結(jié)束以后仍能繼續(xù)存在。
什么是事件循環(huán)呢?一個(gè)典型的圖形應(yīng)用程序往往花費(fèi)了許多時(shí)間等待用戶操作。在控制程序運(yùn)行的人作出決定(比如點(diǎn)擊鼠標(biāo)或者按下某個(gè)鍵)以前,程序?qū)⒁恢碧幱诳臻e狀態(tài)。當(dāng)發(fā)生這樣的事件時(shí),程序?qū)⒈粏拘巡㈤_(kāi)始工作,執(zhí)行必要的操作以響應(yīng)這一事件。在處理完這一事件后,程序返回到休眠狀態(tài)并等待下一個(gè)事件發(fā)生。事件循環(huán)即:在事件發(fā)生時(shí),程序被喚醒;在執(zhí)行完某個(gè)事件所對(duì)應(yīng)的相應(yīng)的操作后,就處于休眠空閑狀態(tài),等待下一個(gè)事件的發(fā)生。為了降低程序的內(nèi)存空間占用,Cocoa會(huì)在程序開(kāi)始處理事件之前創(chuàng)建一個(gè)自動(dòng)釋放池,并在事件處理結(jié)束后銷(xiāo)毀。這樣可以盡量減少累積的臨時(shí)對(duì)象的數(shù)量。
自動(dòng)釋放池被清理的時(shí)間是完全確定的:要么是在代碼中你自己手動(dòng)銷(xiāo)毀,要么是使用AppKit時(shí)在事件循環(huán)結(jié)束時(shí)銷(xiāo)毀。你不必?fù)?dān)心程序會(huì)隨機(jī)地銷(xiāo)毀自動(dòng)釋放池,也不必保留使用的每一個(gè)對(duì)象,因?yàn)樵谡{(diào)用函數(shù)的過(guò)程中自動(dòng)釋放池不會(huì)被銷(xiāo)毀。(在循環(huán)執(zhí)行過(guò)程中自動(dòng)釋放池對(duì)象不會(huì)被銷(xiāo)毀)
解決累積大量臨時(shí)對(duì)象而導(dǎo)致程序占用內(nèi)存增長(zhǎng)問(wèn)題的方法:在循環(huán)中手動(dòng)創(chuàng)建自動(dòng)釋放池,并在特定時(shí)間銷(xiāo)毀。
自動(dòng)釋放池的分配和銷(xiāo)毀操作代價(jià)很小,因此你甚至可以在循環(huán)的每次迭代中創(chuàng)建一個(gè)新的自動(dòng)釋放池。
自動(dòng)釋放池以棧的形式實(shí)現(xiàn):當(dāng)你創(chuàng)建了一個(gè)新的自動(dòng)釋放池時(shí),它就被添加到棧頂。接收autorelease消息的對(duì)象被放入最頂端的自動(dòng)釋放池中。如果將一個(gè)對(duì)象放入一個(gè)自動(dòng)釋放池中,然后創(chuàng)建一個(gè)新的自動(dòng)釋放池,再銷(xiāo)毀該新建的自動(dòng)釋放池,則這個(gè)自動(dòng)釋放池對(duì)象仍將存在,因?yàn)槿菁{該對(duì)象的自動(dòng)釋放池仍然存在。
1.2.3垃圾回收
Objective-C 2.0引入了自動(dòng)內(nèi)存管理機(jī)制,也稱垃圾回收。Java或者Python等語(yǔ)言的內(nèi)存管理機(jī)制就是垃圾回收。對(duì)于已經(jīng)創(chuàng)建和使用的對(duì)象,當(dāng)你忘記清理時(shí),系統(tǒng)會(huì)自動(dòng)識(shí)別哪些對(duì)象仍在使用,哪些對(duì)象可以回收。
啟用垃圾回收功能非常簡(jiǎn)單,但它是一個(gè)可選擇是否啟用的功能。只需要轉(zhuǎn)到項(xiàng)目信息窗口的Building Settings選項(xiàng)卡,并選擇Required[-fobjc-gc-only]選項(xiàng)即可。-fobjc-gc選項(xiàng)是針對(duì)既支持垃圾回收又支持對(duì)象的保留和釋放的代碼,例如在兩種環(huán)境下都能使用的庫(kù)代碼。
啟用垃圾回收以后,平常的內(nèi)存管理命令都變成了空操作指令,說(shuō)得直白點(diǎn)就是它們不執(zhí)行任何操作。
Objective-C的垃圾回收是一代新型的垃圾回收器。與那些面世已久的對(duì)象相比,新創(chuàng)建的對(duì)象更可能被當(dāng)成垃圾。垃圾回收器定期檢查變量和對(duì)象并且跟蹤它們之間的指針,當(dāng)發(fā)現(xiàn)沒(méi)有任何變量指向某個(gè)對(duì)象時(shí),就將該對(duì)象視為應(yīng)該丟棄的垃圾。最糟糕的事情莫過(guò)于讓指針指向一個(gè)不再使用的對(duì)象。因此如果你在一個(gè)實(shí)例變量中指向某個(gè)對(duì)象,一定要將該實(shí)例變量賦值為nil, 以取消對(duì)該對(duì)象的引用并告知垃圾回收器該對(duì)象可以清理了。
與自動(dòng)釋放池一樣,垃圾回收器也是在事件循環(huán)結(jié)束時(shí)觸發(fā)的。當(dāng)然,如果編寫(xiě)的不是GUI程序,你也可以自己觸發(fā)垃圾回收器。
垃圾回收功能只支持OS X應(yīng)用開(kāi)發(fā),無(wú)法用在iOS應(yīng)用程序上。實(shí)際上在iOS開(kāi)發(fā)中,蘋(píng)果公司建議你不要在自己的代碼中使用autorelease方法,也不要使用會(huì)返回自動(dòng)釋放對(duì)象的一些便利的方法。(比如NSString, stringWith開(kāi)頭的方法都是便利方法)
1.2.4自動(dòng)引用計(jì)數(shù)
垃圾回收機(jī)制會(huì)對(duì)移動(dòng)設(shè)備的可用性產(chǎn)生非常不利的影響,因?yàn)橐苿?dòng)設(shè)備比電腦更私人化,資源更少。
蘋(píng)果公司的解決方案被稱為自動(dòng)引用計(jì)數(shù),即ARC。ARC會(huì)追蹤你的對(duì)象并決定哪一個(gè)仍會(huì)使用哪一個(gè)不會(huì)再用到。如果你啟用了ARC, 只管像平常那樣按需分配并使用對(duì)象,編譯器會(huì)幫你插入retain和release語(yǔ)句,無(wú)需你自己動(dòng)手。垃圾回收器在運(yùn)行時(shí)工作,通過(guò)返回的代碼來(lái)定期檢查對(duì)象。與此相反,ARC是在編譯時(shí)進(jìn)行工作的。它在代碼中插入了合適的retain和release語(yǔ)句,就好像是你自己動(dòng)手寫(xiě)好了所有的內(nèi)存管理代碼。不過(guò),是編譯器替你完成了內(nèi)存管理的工作。程序在運(yùn)行的時(shí)候,retain和release會(huì)像往常一樣發(fā)揮作用。系統(tǒng)不會(huì)知道也不會(huì)關(guān)心這些命令是你寫(xiě)的還是編譯器寫(xiě)的。
ARC是一個(gè)可選的功能,也就是說(shuō)你必須明確地啟用或禁用它。
以下是編寫(xiě)ARC代碼所需的條件:
Xcode4.2以上的版本;
Apple LLVM 3.0以上版本的編譯器;
OS X10.7以上版本的系統(tǒng)。
以下是可以運(yùn)行ARC代碼的設(shè)備必須滿足的條件:
iOS4.0以上的移動(dòng)設(shè)備或OS X10.6以上版本的64位系統(tǒng)的電腦;
歸零弱引用需要iOS 5.0或OS X10.7以上版本的系統(tǒng)。
ARC只對(duì)可保留的對(duì)象指針有效。可保留的對(duì)象指針主要有以下三種:
1.代碼塊指針
2.Objective-C指針
3.通過(guò)attribute((NSObject))類(lèi)型定義的指針
所有其他的指針類(lèi)型,比如Char *和CF對(duì)象(例如CFStringRef)都不支持ARC特性。如果你使用的指針不支持ARC, 那么你將不得不親自手動(dòng)管理它們。ARC可以與手動(dòng)的內(nèi)存管理共同發(fā)揮作用。
如果你想要在代碼中使用ARC, 必須滿足以下三個(gè)條件:
1.能夠確定哪些對(duì)象需要進(jìn)行內(nèi)存管理;
2.能夠表明如何去管理對(duì)象;
3.有可行的辦法傳遞對(duì)象的所有權(quán)。
第一個(gè)條件是對(duì)象的最上層集合知道如何去管理它的子對(duì)象。
第二個(gè)條件是你必須能夠?qū)δ硞€(gè)對(duì)象的保留計(jì)數(shù)器的值進(jìn)行加1或者減1的操作。也就是說(shuō)所有NSObject類(lèi)的子類(lèi)都能進(jìn)行內(nèi)存管理。這包括了大部分你需要管理的對(duì)象。
第三個(gè)條件是在傳遞對(duì)象的時(shí)候,你的程序需要能夠在調(diào)用者和接收者之間傳遞所有權(quán)。
使用ARC特性,從長(zhǎng)遠(yuǎn)來(lái)看,可以幫助你減少煩惱,節(jié)省時(shí)間。
1.有時(shí)用weak會(huì)好一些
當(dāng)用指針指向某個(gè)對(duì)象時(shí),你可以管理它的內(nèi)存(通過(guò)retain和release),也可以不管理。如果你管理了,就擁有對(duì)這個(gè)對(duì)象的強(qiáng)引用。如果你沒(méi)有管理,那么你擁有的則是弱引用。如果你對(duì)一個(gè)屬性使用了assign特性,你便創(chuàng)建了一個(gè)弱引用。
弱引用,有助于處理保留循環(huán)(引用循環(huán),形成環(huán))。強(qiáng)引用:被引用的對(duì)象的保留計(jì)數(shù)器的值加1;弱引用:被引用的對(duì)象的保留計(jì)數(shù)器的值不會(huì)增加。
解決引用循環(huán)問(wèn)題的辦法:采用弱引用(assign/weak)。
歸零弱引用(特殊的弱引用)即讓對(duì)象自己去清空弱引用的對(duì)象。在指向的對(duì)象被釋放之后,這些弱引用就會(huì)被設(shè)置為零(即nil), 就可以像平常的指向nil值的指針一樣被處理。歸零弱引用只在iOS 5和OS X10.7以上的版本中有效。
對(duì)象的歸零弱引用會(huì)在對(duì)象所指向的值失效的時(shí)候自動(dòng)轉(zhuǎn)換成nil。
如果想要使用歸零弱引用,必須明確地聲明它們。有兩種方式可以聲明歸零弱引用:聲明變量時(shí)使用__weak關(guān)鍵字或?qū)傩允褂脀eak特性。
如果你不想在支持弱引用的舊系統(tǒng)上使用ARC, 可以使用__unsafe__unretained關(guān)鍵字和unsafe_unretained特性,它們會(huì)告訴ARC這個(gè)特殊的引用是弱引用。
weak與assign的區(qū)別:
① weak關(guān)鍵字表明該屬性定義了一種“非擁有關(guān)系”。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不保留新值,也不釋放舊值。這一點(diǎn)特性與assign類(lèi)似。然而在屬性所指的對(duì)象遭到摧毀時(shí),weak關(guān)鍵字修飾的屬性值也會(huì)被清空(nil out),而assign關(guān)鍵字修飾的屬性的設(shè)置方法只會(huì)針對(duì)“純量類(lèi)型”(scalar type, 或者說(shuō)基本數(shù)據(jù)類(lèi)型,例如CGFloat或NSInteger等)的簡(jiǎn)單賦值操作。
② assign主要用于基本數(shù)據(jù)類(lèi)型,非OC對(duì)象,而weak必須用于OC對(duì)象。
使用ARC的時(shí)候有兩種命名規(guī)則需要注意:
1.屬性名稱不能以new開(kāi)頭,比如說(shuō)@property NSString *newString是不被允許的。
2.屬性不能只有一個(gè)read-only而沒(méi)有內(nèi)存管理特性。如果你沒(méi)有啟用ARC, 可以使用@property(readonly)NSString *title語(yǔ)句,但如果你啟用了ARC功能,就必須指定由誰(shuí)來(lái)管理內(nèi)存。因?yàn)槟J(rèn)的特性是assign。所以,你可以使用一個(gè)簡(jiǎn)單的修復(fù),使用unsafe_unretained就可以了。
同樣強(qiáng)引用也有自己的__strong關(guān)鍵字和strong特性。
需要注意,內(nèi)存管理的關(guān)鍵字和特性是不能一起使用的,兩者相互排斥。
2.一輛新車(chē)
Xcode提供了一個(gè)簡(jiǎn)單的步驟可以把我們已有的項(xiàng)目轉(zhuǎn)換成支持ARC的。開(kāi)始之前,必須確保垃圾回收機(jī)制沒(méi)有使用,垃圾回收和ARC是無(wú)法一同使用的。
轉(zhuǎn)換過(guò)程一共需要兩步。首先必須確保你的代碼能符合ARC的需求,然后執(zhí)行轉(zhuǎn)換操作。
ARC轉(zhuǎn)換是一個(gè)單程的操作。一旦你轉(zhuǎn)換成ARC版本,就不可以恢復(fù)了。
因?yàn)锳RC是基于文件進(jìn)行工作的,所以你可以選在在同一個(gè)項(xiàng)目中經(jīng)過(guò)ARC轉(zhuǎn)換和未經(jīng)過(guò)轉(zhuǎn)換的文件共存。
3.擁有者權(quán)限
指針支持ARC的一個(gè)條件是必須是可保留對(duì)象指針(ROP).這意味著,你不可能簡(jiǎn)單地將一個(gè)ROP表示成不可保留的對(duì)象指針(non-ROP), 因?yàn)橹羔樀乃袡?quán)會(huì)移交。
為了讓ARC便于工作,需要告訴編譯器哪個(gè)對(duì)象是指針的擁有者。為此可以使用一種被稱為橋接轉(zhuǎn)換(bridged cast)的C語(yǔ)言技術(shù)。這是一個(gè)標(biāo)準(zhǔn)的C語(yǔ)言類(lèi)型轉(zhuǎn)換,不過(guò)使用的是其他關(guān)鍵字:__bridge、__bridge_retained和__bridge_transfer。術(shù)語(yǔ)bridge指的是使用不同的數(shù)據(jù)類(lèi)型達(dá)到同一目的的能力。
以下是對(duì)三種橋接轉(zhuǎn)換類(lèi)型的詳細(xì)介紹:
NSString *theString = @"Learn Objective-C";
CFStringRef cfString = (CFStringRef)theString;
1.(__bridge類(lèi)型)操作符:這種類(lèi)型的轉(zhuǎn)換會(huì)傳遞指針但不會(huì)傳遞它的所有權(quán)。在上面的代碼中,操作符是theString , 而類(lèi)型是CFStringRef。如果你使用了這個(gè)關(guān)鍵字,則一個(gè)指針是ROP, 而另一個(gè)不是。在這種情況下指針的所有權(quán)仍會(huì)留在操作符上。
cfString = (__bridge CFStringRef)theString;
cfString接收了指令,但指針的所有權(quán)仍由theString保留。
2.(_bridge_retained類(lèi)型)操作符:使用這種類(lèi)型,所有權(quán)會(huì)轉(zhuǎn)移到non-ROP上。與上一個(gè)相同,一個(gè)指針是ROP,另一個(gè)則不是。因?yàn)锳RC只會(huì)注意到ROP, 所以你要在不用的時(shí)候釋放它。這個(gè)轉(zhuǎn)換類(lèi)型會(huì)給對(duì)象的保留計(jì)數(shù)器加1,所以你必須要讓它減1,這與標(biāo)準(zhǔn)的內(nèi)存管理方式相同。
以下是使用了這種轉(zhuǎn)換類(lèi)型的代碼示例:
cfString = (__bridge__retained CFStringRef)theString
在這個(gè)示例中,cfString字符串擁有指針并且它的保留計(jì)數(shù)器加1。你要使用retain和release來(lái)管理它的內(nèi)存。
3.(__bridge_transfer類(lèi)型)操作符: 這種轉(zhuǎn)換類(lèi)型與上一個(gè)相反,它把所有權(quán)交給ROP。在這個(gè)示例中,ARC擁有對(duì)象并確保它會(huì)像ARC對(duì)象一樣得到釋放。
另一個(gè)限制是結(jié)構(gòu)體(struct)和集合體(union)不能使用ROP作為成員。
以下是不能對(duì)ARC管理的對(duì)象調(diào)用的管理方法:
retain
retainCount
release
autorelease
dealloc
因?yàn)槟阌袝r(shí)需要釋放不支持ARC的對(duì)象或執(zhí)行其他清理操作,所以仍要實(shí)現(xiàn)dealloc方法,但是不能直接調(diào)用[super dealloc]。
以下是不能對(duì)ARC對(duì)象進(jìn)行重寫(xiě)的方法:
retain
retainCount
release
autorelease
1.3異常
什么是異常?異常就是意外事件,比如數(shù)組溢出,因?yàn)槌绦虿恢涝趺刺幚砭蜁?huì)擾亂程序流程。
當(dāng)發(fā)生這種情況時(shí),程序可以創(chuàng)建一個(gè)異常對(duì)象,讓它在運(yùn)行時(shí)系統(tǒng)中計(jì)算出接下來(lái)該怎么做。Cocoa中使用NSException類(lèi)來(lái)表示異常。Cocoa要求所有的異常必須是NSException類(lèi)型的異常,雖然你可以通過(guò)其他對(duì)象拋出異常,但Cocoa并不會(huì)處理它們。此外,你也可以創(chuàng)建NSException的子類(lèi)來(lái)作為你自己的異常。
異常處理的真正目的是處理程序中生成的錯(cuò)誤。Cocoa框架處理錯(cuò)誤的方式通常是退出程序。為了找到出錯(cuò)的原因,你應(yīng)該拋出并捕捉代碼中的異常。
在運(yùn)行時(shí)系統(tǒng)中創(chuàng)建并處理異常的行為被稱為拋出異常,或者說(shuō)是提出異常。
處理被拋出的異常的行為被稱為捕捉異常。
如果一個(gè)異常被拋出但沒(méi)有捕捉到,程序會(huì)在異常斷點(diǎn)處停止運(yùn)行并通知有這個(gè)異常。
1.3.1與異常有關(guān)的關(guān)鍵字
異常的所有關(guān)鍵字都是以@開(kāi)頭的。以下是它們的各自作用。
@try: 定義用來(lái)測(cè)試的代碼塊以決定是否要拋出異常。
@catch(): 定義用來(lái)處理已拋出異常的代碼塊。接收一個(gè)參數(shù),通常是NSEXception類(lèi)型,但也可能是其他類(lèi)型。
@finally: 定義無(wú)論是否有拋出異常都會(huì)執(zhí)行代碼塊,這段代碼總是會(huì)執(zhí)行的。
@throw:拋出異常。
為了確保Cocoa能夠正常處理異常,你應(yīng)該只用NSException對(duì)象來(lái)拋出異常。雖然你也可以用其他對(duì)象(除了NSException實(shí)例以外)拋出異常,但并不是所有的Cocoa框架都會(huì)捕捉其他對(duì)象拋出的異常。
1.3.2捕捉不同類(lèi)型的異常
你可以根據(jù)需要處理的異常類(lèi)型使用多個(gè)@catch代碼塊。處理代碼應(yīng)該按照從具體到抽象的順序排序,并在最后使用一個(gè)通用的處理代碼。
C語(yǔ)言程序員經(jīng)常會(huì)在異常處理代碼中使用setjmp和longjmp語(yǔ)句。你不能使用setjmp和longjmp來(lái)跳出@try代碼塊,但可以使用goto和return語(yǔ)句退出異常處理代碼。
1.3.3拋出異常
當(dāng)程序檢測(cè)到了異常,就必須向處理它的代碼塊(有時(shí)叫做異常處理代碼)報(bào)告這個(gè)異常。通知異常的過(guò)程被稱為拋出(或提出)異常。
程序會(huì)創(chuàng)建一個(gè)NSException實(shí)例來(lái)拋出異常,并會(huì)使用以下兩種技術(shù)之一:
使用"@throw異常名:"語(yǔ)句來(lái)拋出異常;
向某個(gè)NSException對(duì)象發(fā)送raise消息。
兩種方法都可以使用,但不要兩種都使用。兩種方法的區(qū)別是raise只對(duì)NSException對(duì)象有效,而@throw也可以用在其他對(duì)象上。
你通常會(huì)在異常處理代碼中拋出異常。代碼可以通過(guò)再發(fā)送一次raise消息或使用@throw關(guān)鍵字來(lái)通知異常。
在@catch異常處理代碼中,你可以重復(fù)拋出異常而無(wú)需指定異常對(duì)象。
與當(dāng)前@catch異常處理代碼相關(guān)的@finally代碼塊會(huì)在@throw引發(fā)下一個(gè)異常處理調(diào)用之前執(zhí)行代碼,因?yàn)锧finally是在@throw發(fā)生之前調(diào)用的。
Objective-C的異常處理機(jī)制與C++的異常機(jī)制兼容。
1.3.4異常也需要內(nèi)存管理
如果代碼中有異常,內(nèi)存管理執(zhí)行起來(lái)會(huì)比較復(fù)雜。
比如遇到一種常見(jiàn)的問(wèn)題:在某一個(gè)對(duì)象未被釋放之前,程序出現(xiàn)異常,從某一個(gè)方法中跳出并尋找異常處理代碼。一種簡(jiǎn)單的解決辦法就是使用@try和@finally代碼塊,因?yàn)锧finally總是會(huì)執(zhí)行的,所以它可以在里面進(jìn)行清理工作。這種方式也可以用在C語(yǔ)言類(lèi)型的內(nèi)存管理上,比如malloc或free。
1.3.5異常和自動(dòng)釋放池
異常處理有時(shí)會(huì)遇到異常對(duì)象被自動(dòng)釋放的小問(wèn)題。因?yàn)槟悴恢涝撌裁磿r(shí)候釋放,所以異常總是作為自動(dòng)釋放對(duì)象而創(chuàng)建。當(dāng)自動(dòng)釋放池銷(xiāo)毀了以后,自動(dòng)釋放池中所有的對(duì)象也會(huì)被銷(xiāo)毀,包括異常。
比如遇到一種常見(jiàn)的問(wèn)題:自動(dòng)釋放池的釋放早于異常通知,而當(dāng)異常再次被拋出的時(shí)候,它會(huì)變成可怕的僵尸異常。(即:異常對(duì)象已經(jīng)被釋放,而異常發(fā)生的時(shí)候會(huì)再次拋出異常。) 與之對(duì)應(yīng)的簡(jiǎn)單的解決辦法是:在自動(dòng)釋放池外保留異常對(duì)象,而在自動(dòng)釋放池被釋放之后,它也會(huì)同當(dāng)前池一同被釋放。
內(nèi)存管理,或者說(shuō)ARC/MRC管理的范圍: 管理所有OC對(duì)象,對(duì)基本數(shù)據(jù)類(lèi)型無(wú)效,因?yàn)镺C對(duì)象和其他數(shù)據(jù)類(lèi)型在系統(tǒng)中存儲(chǔ)的位置不一樣,其他數(shù)據(jù)類(lèi)型和局部變量主要存儲(chǔ)于棧區(qū)(因?yàn)榛緮?shù)據(jù)類(lèi)型占用的存儲(chǔ)空間是固定的,一般存放于棧區(qū)),而對(duì)象存儲(chǔ)于堆中。當(dāng)代碼塊結(jié)束時(shí),代碼塊所涉及到的所有局部變量會(huì)自動(dòng)彈棧清空,指向?qū)ο蟮闹羔樢矔?huì)被回收,這時(shí)對(duì)象就沒(méi)有指針指向,但依然存在于堆內(nèi)存中,造成內(nèi)存泄露。即:對(duì)象存儲(chǔ)于堆中,而指向?qū)ο蟮闹羔槾鎯?chǔ)于棧中。
小結(jié):
本章介紹了Cocoa的內(nèi)存管理方法:retain、release和autorelease, 還討論了垃圾回收和自動(dòng)引用技術(shù)(ARC, 蘋(píng)果最新的內(nèi)存管理技術(shù))
每個(gè)對(duì)象都維護(hù)一個(gè)保留計(jì)數(shù)器。對(duì)象被創(chuàng)建時(shí),其保留計(jì)數(shù)器的值為1;對(duì)象被保留時(shí),其保留計(jì)數(shù)器的值加1;對(duì)象被釋放時(shí),其保留計(jì)數(shù)器的值減1;當(dāng)保留計(jì)數(shù)器的值歸0時(shí),對(duì)象被銷(xiāo)毀。在銷(xiāo)毀對(duì)象時(shí),首選調(diào)用對(duì)象的dealloc方法,然后回收其占用的內(nèi)存以供其他對(duì)象使用。
Cocoa有三個(gè)關(guān)于對(duì)象及其保留計(jì)數(shù)器的規(guī)則:
如果使用new、alloc或copy操作獲得了一個(gè)對(duì)象,則該對(duì)象的保留計(jì)數(shù)器的值為1。
如果通過(guò)其他方法獲得一個(gè)對(duì)象,則假設(shè)該對(duì)象的保留計(jì)數(shù)器的值為1,而且已經(jīng)設(shè)置為自動(dòng)釋放。
如果保留了某對(duì)象,則必須保持retain方法和release方法的使用次數(shù)相等。
通常ARC會(huì)在編譯過(guò)程中通過(guò)插入這些語(yǔ)句來(lái)幫助你執(zhí)行保留或釋放操作。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 29.理解引用計(jì)數(shù) Objective-C語(yǔ)言使用引用計(jì)數(shù)來(lái)管理內(nèi)存,也就是說(shuō),每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,736評(píng)論 1 3
  • 內(nèi)存管理是程序在運(yùn)行時(shí)分配內(nèi)存、使用內(nèi)存,并在程序完成時(shí)釋放內(nèi)存的過(guò)程。在Objective-C中,也被看作是在眾...
    蹲瓜閱讀 3,361評(píng)論 1 8
  • 在OC這種面向?qū)ο蟮恼Z(yǔ)言中,內(nèi)存管事是個(gè)重要概念。要想用一門(mén)語(yǔ)言寫(xiě)出內(nèi)存使用效率高而且又沒(méi)有bug的代碼,就得掌握...
    竹與豆閱讀 444評(píng)論 0 0
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,644評(píng)論 1 32
  • 古語(yǔ)有云:冤冤相報(bào)何時(shí)了! 今天我想談一談一種冤冤相報(bào)、仇恨越來(lái)越深的心理學(xué)效應(yīng):海格力斯效應(yīng)。 海格力斯效應(yīng)是一...
    明悅心理閱讀 1,863評(píng)論 2 10

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