內(nèi)存優(yōu)化

理解引用計數(shù):

Objective-C使用引用計數(shù)來管理內(nèi)存,引用技術(shù)機(jī)構(gòu)下,對象有個計時器用以表示多少個事物想令此對象繼續(xù)存活下去,即“引用計數(shù)”。

對象創(chuàng)建出來時,其保留計數(shù)至少為1,當(dāng)保留計數(shù)歸零時,對象所占內(nèi)存就會被系統(tǒng)回收。

對象如果持有指向其他對象的強(qiáng)引用,那么前者就“擁有”后者,即被持有對象引用計數(shù)遞增。如果按“引用樹”回溯,會發(fā)現(xiàn)一個“根對象”,iOS應(yīng)用程序中找個對象是UIApplication對象,是應(yīng)用程序啟動時創(chuàng)建的單例。

Objective-C 中調(diào)用 alloc 方法所返回的對象由調(diào)用者所擁有,不過這個對象的引用計數(shù)不一定就是1,在 alloc 或 initWithInt: 方法的實現(xiàn)代碼中,也許有其他對象也持有該對象,所以應(yīng)該說這個對象的引用計數(shù)至少為1。絕不應(yīng)該說保留計數(shù)一定是某個值,只能說某一操作是遞增了其引用計數(shù)還是遞減了引用計數(shù)。

對象所占內(nèi)存在解除分配(deallocated)后,只是放回可用內(nèi)存池(avaiable pool),所以在該對象內(nèi)存被覆寫之前,該對象仍然有效,使用它不會導(dǎo)致程序崩潰。這就是因為過早釋放對象而導(dǎo)致的內(nèi)存問題,程序可能運行正常也可能奔潰,所以這樣的bug很難調(diào)試。

解決:調(diào)用完release之后清空指針,即讓指針指向 nil,這就保證不會出現(xiàn)指向無效對象的指針。

以ARC簡化引用計數(shù):

引用計數(shù)仍然執(zhí)行,不過是由ARC自動添加保留與釋放操作;ARC中不能調(diào)用以下方法:

  • retain
  • release
  • autorelease
  • dealloc

ARC在調(diào)用這些方法時,是直接調(diào)用其底層C語言版本,:objc_retain

若方法名以下列詞語開頭,則其返回的對象歸調(diào)用者所有,要負(fù)責(zé)釋放方法返回的對象:

  • alloc
  • new
  • copy
  • mutableCopy

ARC大法好...
對于MRC部分代碼,使用編譯參數(shù) -fno-objc-arc 可以對該部分文件關(guān)閉ARC。

在應(yīng)用程序中可用使用下列修飾符改變局部變量與實例變量的語義:

  • __strong:默認(rèn)語義,保留此值。
  • __unsafe_unretained:不保留此值,這么做可能不安全,因為等到再次使用該變量時,其對象可能已經(jīng)被回收。
  • __weak:不保留此值,但是變量可以安全使用,如果系統(tǒng)將對象回收,那么變量也會自動清空。
  • __autoreleasing:把對象“pass by reference”給方法時,使用此修飾符,在方法返回時此值自動釋放。

常需要給局部變量加上修飾符,用以打破block所引入的“保留環(huán)”,block會自動保留其所捕獲的全部對象,如果其中某個對象同時又保留了block,則可能導(dǎo)致“保留環(huán)”。
解決:這種情況下可以使用__weak局部變量打破“保留環(huán)”。

注意:ARC只負(fù)責(zé)管理Objective-C對象的內(nèi)存,CoreFoundation對象不歸ARC管理,需要開發(fā)者適時調(diào)用CFRetain/CFRelease。

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

不要自己調(diào)用dealloc方法,在dealloc方法中只做兩件事:手工釋放CoreFoundation對象;把原來配置過的觀測行為清理掉。

對于開銷較大或系統(tǒng)內(nèi)稀缺的資源,我們要實現(xiàn)另一個清理方法,當(dāng)應(yīng)用程序使用完資源對象后立即清理(如socket的close方法)如果沒有適時調(diào)用close清理方法,則需要在dealloc中補(bǔ)上這次調(diào)用:

- (void)close{
   /* 清理資源 */
    _close = YES ; 
}

- (void)dealloc{
    if ( !_closed ) {
        NSLog ( @“ERROR: close was not called before dealloc !” ) ;
        [ self close ] ; 
    }
}

注意:不要在dealloc中隨意調(diào)用其他方法!

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

幾個對象以某種方式相互引用,從而形成“環(huán)”,導(dǎo)致內(nèi)存泄漏。
避免保留環(huán)的最佳方式就是弱引用,使用 weak 而非 unsafe_unretained 引用可以令代碼更安全:unsafe_unretained 聲明的屬性會一直指向那個已經(jīng)回收的實例,而 weak 聲明的屬性其對象被回收后,它會指向nil。

注意:使用 block 時容易產(chǎn)生“保留環(huán)”,因為 block 會捕獲它范圍里的所有變量

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

自動釋放池可以嵌套使用,以此控制應(yīng)用程序的內(nèi)存峰值使其不致過高。

示例代碼

NSMutableArray *peopleArray = [NSMutableArraynew];
for (int i = 0; i < 1000000; i++) {
    EOCPerson *person = [[EOCPersonalloc] init];
    [peopleArray addObject:person];
}

上述代碼會不斷創(chuàng)造出臨時對象,內(nèi)存中會占用很多不必要的臨時對象,導(dǎo)致程序執(zhí)行 for 循環(huán)時所占內(nèi)存量持續(xù)上升,這些臨時對象釋放后內(nèi)存又突然下降。

解決:增加一個自動釋放池

NSMutableArray *peopleArray = [NSMutableArraynew];       for (int i = 0; i < 1000000; i++) {        @autoreleasepool {            EOCPerson *person = [[EOCPersonalloc] init];            [peopleArray addObject:person];        }    }

把循環(huán)內(nèi)的代碼包裹在“自動釋放池塊”中,循環(huán)中自動釋放的對象就會在這個池而不是線程的主池里了,這樣應(yīng)用程序在執(zhí)行以上循環(huán)時內(nèi)存峰值會降低。

自動釋放池塊有一定的開銷,是否需要使用取決于具體應(yīng)用程序的內(nèi)存用量,如果不需要盡量不要建立額外的自動釋放池。

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

之前提到被回收的內(nèi)存只是被放回可用內(nèi)存池中,在這塊內(nèi)存被其他內(nèi)容覆寫前仍可以接收消息,也有可能那塊內(nèi)存只被復(fù)用了一部分、甚至可能被另一個有效且存活的對象占據(jù),這種情況下程序時而正常時而奔潰,收到消息的對象不是預(yù)想的對象。
為了調(diào)試這種內(nèi)存問題,可以使用“僵尸對象”功能,啟用該功能后,運行期系統(tǒng)會把所有已回收的實例轉(zhuǎn)化為“僵尸對象”,而不會真正回收它們;僵尸對象如果收到消息會拋出異常,并說明發(fā)送過來的消息、描述回收前的對象。

開啟“僵尸對象”:Product ——> Scheme ——> Edit Scheme ——> Diagnostics ——> Enable Zombie Objects

僵尸對象的原理好長...要點:系統(tǒng)會修改對象的isa指針,令其指向特殊的僵尸類,從而使該對象變成僵尸對象。僵尸類能夠響應(yīng)所有selector,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序。

最后:不要使用retainCount,在ARC下也無法使用。

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

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

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