編寫高質(zhì)量代碼的52個(gè)有效方法(五)—內(nèi)存管理

outstanding.png

29.理解引用計(jì)數(shù)

引用計(jì)數(shù)工作原理:在引用計(jì)數(shù)架構(gòu)下,對(duì)象有個(gè)計(jì)數(shù)器,用以表示當(dāng)前有多少個(gè)事物想令此對(duì)象繼續(xù)存活下去。這在Objective-C中叫做“保留計(jì)數(shù)”(retain count),不過也可以叫“引用計(jì)數(shù)”(reference count)。NSObject協(xié)議聲明了下面三個(gè)方法用于操作計(jì)數(shù)器,以遞增或遞減其值:

  1. retain 遞增保留計(jì)數(shù)
  2. release 遞減保留計(jì)數(shù)
  3. autorelease 待稍后清理“自動(dòng)釋放池”(autorelease pool)時(shí),再遞減保留計(jì)數(shù)。

一個(gè)對(duì)象從創(chuàng)建到釋放的整個(gè)過程:對(duì)象創(chuàng)建出來時(shí),其保留計(jì)數(shù)至少為1.若想令其繼續(xù)存活,則調(diào)用retain方法。要是某部分代碼不再使用此對(duì)象,不想令其繼續(xù)存活,那就調(diào)用release或autorelease方法。最終當(dāng)保留計(jì)數(shù)歸零時(shí),對(duì)象就回收了(deallocated),也就是說,系統(tǒng)會(huì)將其占用的內(nèi)存標(biāo)記為“可重用”(reuse)。此時(shí),所有指向該對(duì)象的引用也都變得無效了。

要點(diǎn):

  1. 引用計(jì)數(shù)機(jī)制通過可以遞增遞減的計(jì)數(shù)器來管理內(nèi)存。對(duì)象創(chuàng)建好之后,其保留計(jì)數(shù)至少為1.若保留計(jì)數(shù)為正,則對(duì)象繼續(xù)存活。當(dāng)保留計(jì)數(shù)將為0時(shí)2,對(duì)象就被銷毀了。
  2. 在對(duì)象生命周期中,其余對(duì)象通過應(yīng)用來保留或釋放此對(duì)象。保留與釋放操作分別會(huì)遞增及遞減保留計(jì)數(shù)。

30.以ARC簡(jiǎn)化引用計(jì)數(shù)

由于ARC會(huì)自動(dòng)執(zhí)行retain、release、autorelease等操作,所有直接在ARC下調(diào)用這些內(nèi)存管理方法是非法的。具體來說,不能調(diào)用下列方法:

  1. retain
  2. release
  3. autorelease
  4. dealloc

要點(diǎn):

  1. 有ARC之后,程序員就無需擔(dān)心內(nèi)存管理問題了。使用ARC來編程,可省去類中的許多“樣板代碼”。
  2. ARC管理對(duì)象生命期的辦法基本上就是:在合適的地方插入“保留”及“釋放”操作。在ARC環(huán)境下,變量的內(nèi)存管理語義可以通過修飾符指明,而原來則需要手工執(zhí)行“保留”和“釋放”操作。
  3. 由方法所返回的對(duì)象,其內(nèi)存管理語義總是通過方法名來體現(xiàn)。ARC將此確定為開發(fā)者必須遵守的規(guī)則。
  4. ARC只負(fù)責(zé)管理Objective-C對(duì)象的內(nèi)存。尤其要注意:CoreFoundation對(duì)象不歸ARC管理,開發(fā)者必須適時(shí)調(diào)用CFRetain/CFRelease.

31.在dealloc方法中只釋放引用并解除監(jiān)聽

//ARC模式下的內(nèi)存管理
- (void)dealloc{
    CFRelease(coreFoundationObject);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

要點(diǎn):

  1. 在dealloc方法里,應(yīng)該做的事情就是釋放指向其他對(duì)象的引用,并取消原來訂閱的“鍵值觀測(cè)”(KVO)或NSNotificationCenter等通知,不要做其他事情。
  2. 如果對(duì)象持有文件描述符等系統(tǒng)資源,那么應(yīng)該專門編寫一個(gè)方法來釋放此種資源。這樣的類要和其他使用約定:用完資源后必須調(diào)用close方法。
  3. 執(zhí)行異步任務(wù)的方法不應(yīng)再dealloc里調(diào)用;只能在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在dealloc里調(diào)用,因?yàn)榇藭r(shí)對(duì)象已處于正在回收的狀態(tài)了。

32.編寫“異常安全代碼”時(shí)留意內(nèi)存管理問題

    Object *obj = [Object new];
    
    @try {
        [obj doSomethingMayThrow];
    } @catch (NSException *exception) {
        NSLog(@"wawo,拋異常了");
    } @finally { 
        [obj close];
    }

@finally:無論是否拋出異常,其中的代碼都保證會(huì)運(yùn)行,且只運(yùn)行一次。

要點(diǎn):

  1. 捕獲異常時(shí),一定要注意將try塊內(nèi)所創(chuàng)立的對(duì)象清理干凈
  2. 在默認(rèn)情況下,ARC不生成安全處理一次所需的清理代碼。開啟編譯器標(biāo)志后(-fobjc-arc-exceptions),可生成這種代碼,不過會(huì)導(dǎo)致應(yīng)用程序變大,而且會(huì)降低運(yùn)行效率。

33.以弱引用避免保留環(huán)

保留環(huán)也就是我們常說的循環(huán)引用。保留環(huán)通常會(huì)造成內(nèi)存泄漏。

垃圾回收:Mac OS X平臺(tái)的Objective-C程序有個(gè)選項(xiàng),可以啟用垃圾收集器(garbage collector),它會(huì)檢測(cè)保留環(huán),若發(fā)現(xiàn)外界不再引用其中的對(duì)象,則將之回收。但是,從Mac OS X 10.8開始,垃圾收集機(jī)制就廢棄了,而且iOS系統(tǒng)從未支持過這項(xiàng)功能。因此,從一開始編碼時(shí)要注意別出現(xiàn)保留環(huán)。避免保留環(huán)的最佳方式就是弱引用。這種引用經(jīng)常用來表示“非擁有關(guān)系”(nonowning relationship)。將屬性聲明為unsafe_unretained或weak。

用unsafe_unretained修飾的屬性特質(zhì),其語義同assign特質(zhì)等價(jià)。然而,assign通常只用于“基本類型”(int float 結(jié)構(gòu)體等), unsafe_unretained則多用于對(duì)象類型。這個(gè)詞本身就表明其所修飾的屬性可能無法安全使用。

Objective-C中還有一項(xiàng)與ARC相伴的運(yùn)行期特性,可以令開發(fā)者安全使用弱引用:這就是weak屬性特質(zhì),它與unsafe_unretained的作用完全相同。然而,只要系統(tǒng)把屬性回收,屬性值就會(huì)自動(dòng)設(shè)為nil。

區(qū)別:unsafe_unretained與weak屬性,在其所指的對(duì)象回收以后表現(xiàn)出來的行為不同。當(dāng)指向?qū)嵗囊靡瞥螅瑄nsafe_unretained屬性仍然指向那個(gè)已經(jīng)回收的實(shí)例,而weak屬性則指向nil。

要點(diǎn):

  1. 將某些引用設(shè)為weak,可避免出現(xiàn)保留環(huán)。
  2. weak引用可以自動(dòng)清空,也可以不自動(dòng)清空。自動(dòng)清空(autonilling)是隨著ARC而引入的新特性,由運(yùn)行期系統(tǒng)來實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上,可以隨意讀取其數(shù)據(jù),因?yàn)檫@種引用不會(huì)指向已經(jīng)回收的對(duì)象。

34.以“自動(dòng)釋放池塊”降低內(nèi)存峰值

Objective-C對(duì)象的生命周期取決于其引用計(jì)數(shù)。在Objective-C的引用計(jì)數(shù)架構(gòu)中,有一項(xiàng)特性叫做“自動(dòng)釋放池”(autorelease pool)。釋放對(duì)象有兩種方式:一種是調(diào)用release方法,使其保留計(jì)數(shù)立即遞減;另一種是調(diào)用autorelease方法;將其加入“自動(dòng)釋放池”中。自動(dòng)釋放池用于存放那些需要在稍后某個(gè)時(shí)刻釋放的對(duì)象。清空(drain)自動(dòng)釋放池時(shí),系統(tǒng)會(huì)像其中的對(duì)象發(fā)送release消息。

int main(int argc, char * argv[]) {

    @autoreleasepool {
    
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        
    }
    
}

寫在main函數(shù)里的這個(gè)自動(dòng)釋放池可以理解為最外圍捕捉全部自動(dòng)釋放對(duì)象所用的池。

要點(diǎn):

  1. 自動(dòng)釋放池排布在棧中,對(duì)象收到autorelease消息后,系統(tǒng)將其放入最頂端的池里。
  2. 合理運(yùn)用自動(dòng)釋放池,可降低應(yīng)用程序的內(nèi)存峰值
  3. @autoreleasepool這種新式寫法能創(chuàng)建出更為輕便的自動(dòng)釋放池。

35.用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題

向已經(jīng)回收的對(duì)象發(fā)送消息是不安全的,也就是一個(gè)空指針調(diào)用方法會(huì)終止應(yīng)用程序,同時(shí)打印 message sent to deallocated instance ox58s5a5fg5o 類似的信息。開啟 “僵尸對(duì)象”(Zombie Object)功能,運(yùn)行期系統(tǒng)會(huì)把所有已經(jīng)回收的實(shí)例轉(zhuǎn)化成特殊的“僵尸對(duì)象”,而不會(huì)真正回收它們。僵尸對(duì)象收到消息后,會(huì)拋出異常,其中準(zhǔn)確說明了發(fā)送過來的消息,并描述了回收之前的那個(gè)對(duì)象。

開發(fā)方式:編輯應(yīng)用程序的scheme,在對(duì)話框左側(cè)選擇“Run”,然后切換至“Diagnostics”分頁,最后勾選“Enable Zombie Objects”選項(xiàng)。

要點(diǎn):

  1. 系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對(duì)象。通過環(huán)境變量NSZombieEnabled可開啟此功能。
  2. 系統(tǒng)會(huì)修改對(duì)象的isa指針,令其指向特殊的僵尸類,從而使對(duì)象變成僵尸對(duì)象。僵尸類能夠響應(yīng)所有的選擇子(方法),響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序。

36.不要使用retainCount

//MRC
- (NSInteger)retainCount;

Objective-C通過引用計(jì)數(shù)來管理內(nèi)存。每個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器,其值表明還有多少個(gè)其他對(duì)象想令此對(duì)象繼續(xù)存活。對(duì)象創(chuàng)建好之后,其保留計(jì)數(shù)大于0.保留與釋放操作分別會(huì)使改計(jì)數(shù)遞增及遞減。當(dāng)計(jì)數(shù)變?yōu)?時(shí),對(duì)象就為系統(tǒng)所回收并摧毀了。然而ARC已經(jīng)將此方法廢棄了。實(shí)際上,在ARC中調(diào)用,編譯器就會(huì)報(bào)錯(cuò),這和在ARC中調(diào)用retain、release、autorelease方法時(shí)的情況一樣。

要點(diǎn):

  1. 對(duì)象的保留計(jì)數(shù)看似有用,實(shí)則不然,因?yàn)槿魏谓o定時(shí)間上的“絕對(duì)保留計(jì)數(shù)”(absolute retain count)都無法反映對(duì)象生命期的全貌。
  2. 引入ARC之后,retainCount方法就正式廢止了,在ARC下調(diào)用該方法會(huì)導(dǎo)致編譯器報(bào)錯(cuò)。



PDF格式的資料來自iOS開發(fā)交流群、感覺作者的貢獻(xiàn),對(duì)于知識(shí)的系統(tǒng)歸納總結(jié)很有幫助。
編寫高質(zhì)量代碼的52個(gè)有效方法
編寫高質(zhì)量代碼的52個(gè)有效方法(一)—熟悉OC
編寫高質(zhì)量代碼的52個(gè)有效方法(二)—對(duì)象、消息、運(yùn)行期
編寫高質(zhì)量代碼的52個(gè)有效方法(三)—接口與API設(shè)計(jì)
編寫高質(zhì)量代碼的52個(gè)有效方法(四)—協(xié)議與分類
編寫高質(zhì)量代碼的52個(gè)有效方法(五)—內(nèi)存管理
編寫高質(zhì)量代碼的52個(gè)有效方法(六)—塊與大中樞派發(fā)
編寫高質(zhì)量代碼的52個(gè)有效方法(七)---系統(tǒng)框架

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容