《Effective Objective-C 2.0》5.內(nèi)存管理

第5章 內(nèi)存管理

第29條:理解引用計數(shù)

  • Objective-C 語言使用引用計數(shù)來管理內(nèi)存,每個對象都有一個可以遞增或遞減的計數(shù)器蝇恶。

  • 使用 ARC 時拳魁,所有與引用計數(shù)有關(guān)的方法都無法編譯。

    - (instancetype)retain OBJC_ARC_UNAVAILABLE;
    - (oneway void)release OBJC_ARC_UNAVAILABLE;
    - (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
    - (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
    
    - (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
    

引用計數(shù)工作原理

  • 對象有個計數(shù)器撮弧,表示當前有多少個事物想令此對象繼續(xù)存活潘懊。這在 Objective-C 中叫做 保留計數(shù)(retain count)或者 引用計數(shù)(reference count)。
  • 對象創(chuàng)建出來時贿衍,其引用計數(shù)至少為1授舟。
  • 不應(yīng)該說引用計數(shù)一定是某個值,而應(yīng)該說執(zhí)行的操作遞增/遞減了該計數(shù)贸辈。
  • 在 iOS 應(yīng)用程序中释树,根對象為 UIApplication 單例對象。

NSObject 協(xié)議聲明了三個方法用于操作計數(shù)器:

  1. retain :遞增引用計數(shù)。
  2. release : 遞減引用計數(shù)奢啥。
  3. autorelease: 待稍后清理“自動釋放池”(autorelease pool)時秸仙,再遞減引用計數(shù)。
NSMutableArray *array = [[NSMutableArray alloc] init];

NSNumber *number = [[NSNumber alloc] initWithInt:1337]; // retainCount >= 1
[array addObject:number]; // retainCount >= 2
[number release]; // retainCount >= 1
number = nil;  // 避免懸掛指針(指向無效對象的指針)

// do somethind with "array"
[array release];

屬性存取方法中的內(nèi)存管理

// strong 屬性:
- (void)setFoo:(id)foo {
    // 順序很重要桩盲,如果先 release寂纪,則此對象將被永久回收
    [foo retain];   // 先保留新值。
    [_foo release]; // 再釋放舊值赌结。
    _foo = foo;     // 更新實例變量捞蛋,令其指向新值。
}

自動釋放池

  • autorelease 方法會在稍后遞減計數(shù)柬姚,通常是在下一次 事件循環(huán)(event loop)時遞減拟杉。
  • autorelease 能延長對象的生命周期,保證對象在 跨越方法調(diào)用邊界 后依然可以存活一段時間伤靠。

示例:

- (NSString *)stringValue {
    NSString *str = [[NSString alloc]
                        initWithFormat:@"I am this:%@",self]; // retainCount >= 1
    return str; // retainCount >= 2
}

此處需要使用 autorelease 釋放對象:

- (NSString *)stringValue {
    NSString *str = [[NSString alloc]
                        initWithFormat:@"I am this:%@",self];
    return [str autorelease];
}

引用循環(huán)/保留環(huán)

引用循環(huán):呈環(huán)狀相互引用的多個對象捣域,內(nèi)存無法正常釋放,導(dǎo)致內(nèi)存泄漏宴合。

解決方法:弱引用(weak reference)焕梅、從外界命令循環(huán)中的某個對象不再保留另外一個對象。

要點

  • 引用計數(shù)機制通過可以遞增遞減的計數(shù)器來管理內(nèi)存卦洽。對象創(chuàng)建好后贞言,其保留計數(shù)至少為1。若保留計數(shù)為正阀蒂,則對象繼續(xù)存活该窗。當保留計數(shù)降為0時,對象就被銷毀了蚤霞。
  • 在對象生命期中酗失,其余對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增及遞減保留計數(shù)昧绣。

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

  • ARC 原理:靜態(tài)分析器(static analyzer)可以查明內(nèi)存管理問題规肴,也可以預(yù)先加入適當?shù)谋A艋蜥尫挪僮饕员苊鈨?nèi)存管理問題。
  • 在 ARC 中夜畴,不能直接調(diào)用 retain拖刃、releaseautorelease贪绘、dealloc 方法兑牡。
  • ARC 會直接調(diào)用底層 C 語言函數(shù)自動管理內(nèi)存。
  • ARC 會自動調(diào)用“保留”與“釋放”方法税灌。
  • ARC 包含運行期組件均函。

使用 ARC 時必須遵循的方法命名規(guī)則

若方法名以下列詞語開頭亿虽,則其返回的對象歸調(diào)用者所有:

  • alloc
  • new
  • copy
  • mutableCopy

歸調(diào)用者所有:【調(diào)用上述四種方法的那段代碼】要負責(zé)【釋放方法所返回的對象】。

變量的內(nèi)存管理語義

默認情況下边酒,每個變量都是指向?qū)ο蟮膹娨谩?/p>

修飾符:

  • __strong:默認語義经柴,保留此值。
  • __unsafe_unretained:不保留此值墩朦,這么做可能不安全坯认,因為等到再次使用變量時,其對象可能已經(jīng)回收了氓涣。
  • __weak:不保留此值牛哺,但是變量可以安全使用,因為如果系統(tǒng)把這個對象回收了劳吠,那么變量也會自動清空引润。
  • __autoreleasing:把對象“按引用傳遞”給方法時,使用這個特殊的修飾符痒玩。此值在方法返回時自動釋放淳附。

ARC 如何清理實例變量

  • ARC 會借用 Objective-C++ 的一項特性來生成清理例程。

  • ARC 會自動生成回收對象時所執(zhí)行的代碼蠢古。

  • CoreFoundation 對象不歸 ARC 管理奴曙,開發(fā)者必須適時調(diào)用 CFRetain/CFRelease

    - (void)dealloc {
        CFRelease(coreFoundationObject);
        free(_heapAllocatedMemoryBlob);
    }
    

覆寫內(nèi)存管理方法

在 ARC 環(huán)境下不要覆寫 release 方法草讶。

要點

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

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

  • dealloc 方法主要用于釋放對象所擁有的引用(即釋放所有 Objective-C 對象)诵棵,ARC 會通過自動生成的 .cxx_destruct 方法在 dealloc 中自動添加釋放代碼。
  • 非 Objective-C 對象(如 CoreFoundation 對象)則必須手工釋放祝旷。
  • 還需要清理觀測行為(observation behavior)履澳,注銷通知嘶窄。
  • 開銷較大或系統(tǒng)內(nèi)稀缺資源不應(yīng)在 dealloc 中釋放,當應(yīng)用程序用完資源后應(yīng)及時釋放距贷,還有一個原因是:系統(tǒng)并不保證每個創(chuàng)建出來的對象的 dealloc 都會執(zhí)行柄冲。
- (void)dealloc {
    CFRelease(coreFoundationObject);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
  • 如果對象管理著某些資源,那么在 dealloc 方法中也要調(diào)用“清理方法”忠蝗,以防開發(fā)者忘記清理這些資源现横。

    - (void)close {
        /** clean up resoureces */
        _close = YES;
    }
    
    - (void)dealloc {
        if (!_close) {
            NSLog(@"ERROR:close was not called before dealloc");
            [self close];
        }
    }
    

要點

  • dealloc 方法里,應(yīng)該做的事情就是釋放指向其它對象的引用阁最,并取消原來訂閱的"鍵值觀測"(KVO)或 NSNotificationCenter 等通知戒祠,不要做其他事情。
  • 如果對象持有文件描述符等系統(tǒng)資源速种,那么應(yīng)該專門編寫一個方法來釋放此種資源姜盈。這樣的類要和其使用者約定:用完資源后必須調(diào)用 close 方法。
  • 執(zhí)行異步任務(wù)的方法不應(yīng)在 dealloc 里調(diào)用配阵;只能在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在 dealloc 里調(diào)用馏颂,因為此時對象已處于正在回收的狀態(tài)了。

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

  • 在 Objective-C 中棋傍,異常只應(yīng)在發(fā)生嚴重錯誤后拋出(參見第21條)救拉。
  • try 塊中,如果先保留了某個對象舍沙,釋放它之前又拋出了異常近上,就會導(dǎo)致內(nèi)存泄漏。而且 ARC 不會自動處理這個問題拂铡。
  • 可以在編譯器中開啟 -fobjc-arc-exceptions(默認關(guān)閉)壹无, 讓 ARC 生成安全處理異常所用的附加代碼。
  • Objective-C++模式下感帅,編譯器會自動打開 -fobjc-arc-exceptions標志斗锭。

要點

  • 捕獲異常時,一定要注意將 try 塊內(nèi)所創(chuàng)立的對象清理干凈失球。
  • 在默認情況下岖是,ARC 不生成安全處理異常所需的清理代碼。開啟編譯器標志后实苞,可生成這種代碼豺撑,不過會導(dǎo)致應(yīng)用程序變大,而且會降低運行效率黔牵。

第33條:以弱引用避免保留環(huán)

  • 幾個對象相互引用會導(dǎo)致引用循環(huán)造成內(nèi)存泄露聪轿。
  • 避免引用循環(huán)的最佳方式就是弱引用(weak)。
  • unsafe_unretained(不保留也不釋放)猾浦、assign(通常只用于整體類型陆错,如 int灯抛、float、結(jié)構(gòu)體等)音瓷、weak(屬性被回收后會自動設(shè)置為nil) 之間的區(qū)別对嚼?

要點

  • 將某些引用設(shè)為 weak,可避免出現(xiàn)引用循環(huán)绳慎。
  • weak 引用可以自動清空纵竖,也可以不自動清空。自動清空是隨著ARC而引入的新特性偷线,由 runtime 來實現(xiàn)磨确,在具備自動清空功能的弱引用上,可以隨意讀取其數(shù)據(jù)声邦,因為這種引用不會指向已經(jīng)回收過的對象乏奥。

第34條:以“自動釋放池”降低內(nèi)存峰值

自動釋放池(autorelease pool):存放需要在稍后某個時刻釋放的對象。

釋放對象的兩種方式:

  1. 調(diào)用 release 方法亥曹,使其引用計數(shù)立即遞減邓了;
  2. 調(diào)用 autorelease 方法,將對象加入自動釋放池中媳瞪。清空自動釋放池時骗炉,系統(tǒng)會向其中的對象發(fā)送 release 消息;

創(chuàng)建自動釋放池語法:

@autoreleasepool {
   // ...
}

程序員無需自己創(chuàng)建自動釋放池蛇受,系統(tǒng)自動創(chuàng)建的線程中默認有自動釋放池句葵。除了 main 函數(shù)中:

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

自動釋放池的嵌套:

@autoreleasepool {
    NSString *string = [NSString stringWithFormat:@"hello"];
    @autoreleasepool {
        NSNumber *number = [NSNumber numberWithInteger:1];
    }
}

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

內(nèi)存峰值(high-memory waterline):應(yīng)用程序在某個特定時段內(nèi)的最大內(nèi)存用量乍丈。

NSArray *databaseRecords = /** ... */ ;
NSMutableArray *people = [NSMutableArray new];
// 將循環(huán)內(nèi)的代碼包裹在自動釋放池中
// 系統(tǒng)就會在塊末尾釋放對象,而不是在主線程集中釋放把将。
for (NSDictionary *record in databaseRecords) {
    @autoreleasepool {
        EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
        [people addObject:person];
    }
}

??是否應(yīng)該使用自動釋放池優(yōu)化效率還應(yīng)視情況而定轻专,因為創(chuàng)建自動釋放池本身也有一定的開銷。

@autoreleasepool 語法的另一個好處:每個自動釋放池均有其范圍察蹲,可以避免無意間誤用在自動釋放池中已經(jīng)被系統(tǒng)回收的對象请垛。

要點

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

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

向業(yè)已回收的對象發(fā)送消息是不安全的,可行與否完全取決于對象所占內(nèi)存有沒有為其他內(nèi)容所覆寫

調(diào)試內(nèi)存管理的最佳方式:僵尸對象(Zombie Object)

??原理:啟用僵尸對象調(diào)試功能之后荚坞,運行期系統(tǒng)會把所有已經(jīng)回收的實例轉(zhuǎn)化成特殊的"僵尸對象",而不會真正回收它們菲盾。這種對象所在的核心內(nèi)存無法重用颓影,因此不可能遭到覆寫。僵尸對象收到消息后懒鉴,會拋出異常诡挂,其中準確說明了發(fā)送過來的消息,并描述了回收之前的那個對象临谱。

NszombieEnabled 環(huán)境變量設(shè)為 YES, 即可開啟此功能璃俗。

??位置:Xcode菜單欄 → Product → Scheme → Edit Scheme → Run → Diagnostics診斷選項 → Memory Management → 勾選 Zombie Objects。

?? APP 打包發(fā)布之前一定要取消此勾選悉默!

要點

  • 系統(tǒng)在回收對象時城豁,可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對象抄课。通過環(huán)境變量 NSZombieEnabled 可開啟此功能唱星。
  • 系統(tǒng)會修改對象的 isa 指針,令其指向特殊的僵尸類跟磨,從而使該對象變?yōu)榻┦瑢ο蠹淞摹=┦惸軌蝽憫?yīng)所有的selector,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息抵拘,然后終止應(yīng)用程序哎榴。

第36條:不要使用 retainCount

- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE; // 查詢對象當前的保留計數(shù)

?? retainCount 所返回的保留計數(shù)只是某個給定時間點上的值,該方法并未考慮到自動釋放池的情況僵蛛。

// 錯誤示例:
while ([object retainConut]) {
    [object release]
}
// 錯誤一:沒有考慮到后續(xù)的自動釋放操作尚蝌;
// 錯誤二:retainConut 可能永遠不返回0;

要點

  • 對象的保留計數(shù)看似有用墩瞳,實則不然驼壶,因為任何給定時間點上的"絕對保留計數(shù)"(absoulte retain count)都無法反映對象生命期的全貌。
  • 引入 ARC 后喉酌,retainCount 方法就正式廢止了热凹,在 ARC 下調(diào)用該方法會導(dǎo)致編譯器報錯。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泪电,一起剝皮案震驚了整個濱河市般妙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌相速,老刑警劉巖碟渺,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異突诬,居然都是意外死亡苫拍,警方通過查閱死者的電腦和手機芜繁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绒极,“玉大人骏令,你說我怎么就攤上這事÷⑻幔” “怎么了榔袋?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铡俐。 經(jīng)常有香客問我凰兑,道長,這世上最難降的妖魔是什么审丘? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任吏够,我火速辦了婚禮,結(jié)果婚禮上备恤,老公的妹妹穿的比我還像新娘稿饰。我一直安慰自己,他們只是感情好露泊,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布喉镰。 她就那樣靜靜地躺著,像睡著了一般惭笑。 火紅的嫁衣襯著肌膚如雪侣姆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天沉噩,我揣著相機與錄音捺宗,去河邊找鬼。 笑死川蒙,一個胖子當著我的面吹牛蚜厉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畜眨,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昼牛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了康聂?” 一聲冷哼從身側(cè)響起贰健,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恬汁,沒想到半個月后伶椿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年脊另,在試婚紗的時候發(fā)現(xiàn)自己被綠了导狡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡偎痛,死狀恐怖烘豌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情看彼,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布囚聚,位于F島的核電站靖榕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏顽铸。R本人自食惡果不足惜茁计,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谓松。 院中可真熱鬧星压,春花似錦、人聲如沸鬼譬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽优质。三九已至竣贪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巩螃,已是汗流浹背演怎。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留避乏,地道東北人爷耀。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像拍皮,于是被迫代替她去往敵國和親歹叮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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

  • 29.理解引用計數(shù) Objective-C語言使用引用計數(shù)來管理內(nèi)存春缕,也就是說盗胀,每個對象都有個可以遞增或遞減的計數(shù)...
    Code_Ninja閱讀 1,490評論 1 3
  • 29.理解引用計數(shù) 引用計數(shù)工作原理:在引用計數(shù)架構(gòu)下,對象有個計數(shù)器锄贼,用以表示當前有多少個事物想令此對象繼續(xù)存活...
    037e3257fa3b閱讀 656評論 0 0
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機制票灰。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 1,964評論 1 16
  • 內(nèi)存管理是程序在運行時分配內(nèi)存屑迂、使用內(nèi)存浸策,并在程序完成時釋放內(nèi)存的過程。在Objective-C中惹盼,也被看作是在眾...
    蹲瓜閱讀 3,072評論 1 8
  • 29手报,理解引用計數(shù) 1蚯舱,引用計數(shù)機制通過可以遞增遞減扥計數(shù)器來管理內(nèi)存。對象創(chuàng)建好之后掩蛤,其保留計數(shù)至少為1枉昏。若保留...
    fordring2008閱讀 179評論 0 0