iOS 內存管理(一)

簡書內容都是個人的知識點整理和筆記爷速。

1.Objective-C中的內存管理

應用程序在運行期間通過內存管理完成內存的分配双泪、使用和釋放坞古。而在Objective-C中的內存管理則是在有限的內存資源分配的情況下,通過明確管理對象的生命周期和合理的對象釋放來管理應用程序的內存酱鸭。
在OC中我們所要面對的是一群對象的內存管理吗垮。如果程序運行時一直給對象分配內存而不及時釋放無用的內存,在這樣的情況下程序占用的內存越來越大凹髓,直至內存消耗殫盡烁登,從而導致內存泄漏應用程序崩潰。
Objective-C提供了兩種應用程序內存管理方法蔚舀。
(1)MRR:手動保留釋放(Manual Retain-release)饵沧,通過跟蹤擁有的對象來顯式管理內存。在系統(tǒng)運行時通過實現(xiàn)引用計數(shù)(reference counting)為環(huán)境和對象提供了這樣的內存管理方法赌躺。
(2)ARC:自動引用計數(shù)(Automatic Reference Counting)與MRR一樣都實現(xiàn)了引用計數(shù)的運用狼牺,但系統(tǒng)在編譯時會自動插入相應的內存管理方法調用。


Memory Management

2.內存管理策略

內存管理基本規(guī)則

內存管理模型是依據(jù)對象持有建立的礼患。一個對象擁有一個或多個持有者是钥,只要一個對象存在持有者,那么這個對象就會存在缅叠,反之它將會被銷毀悄泥。我們可以通過alloc、 new肤粱、copy和mutableCopy這些方法創(chuàng)建一個對象弹囚。當一個對象被有效地創(chuàng)建并返回給調用者時,有兩種情況需要使用retain:
(1)在實現(xiàn)方法或初始化時狼犯,將要存儲的對象的所有權作為屬性值余寥;
(2)防止對象作為某些其他操作的副作用而被無效领铐。
最后我們通過發(fā)送釋放消息或自動釋放消息來放棄對象的所有權,以此來釋放對象宋舷。
我們通過一個例子來解釋這個基本規(guī)則:

{
    Person *aPerson = [[Person alloc] init];
    // do something...
    NSString *name = aPerson.fullName;
    // do something...
    [aPerson release];
}

在這個例子中绪撵,Person類對象 aPerson通過alloc被創(chuàng)建,在不再使用時又被release方法被釋放掉祝蝠。因為aPerson的fullName屬性沒有被任何持有方法來存儲音诈,所以name不需要被釋放。

釋放對象

NSObject類定義了一個dealloc方法绎狭,當一個對象沒有所有者并且它的內存被回收的時候细溅,這個方法被自動地調用。 dealloc方法的作用是釋放對象自己的內存儡嘶,并處理其擁有的任何資源喇聊,包括任何對象實例變量的所有權。

@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
 
@implementation Person
// ...
- (void)dealloc
    [_firstName release];
    [_lastName release];
    [super dealloc];
}
@end

在這個代碼例子中蹦狂,當Person類對象被釋放時誓篱,其屬性和實例變量在dealloc方法中被釋放處理。

3.內存管理實際應用

我們可以采取一些可行的步驟來更容易地管理內存凯楔,幫助我們確保程序保持的可靠和健壯性窜骄。

使用方法訪問

當我們的類中存在一個作為屬性的對象,我們必須確保在使用這個對象的時候沒有被釋放摆屯。因此在設置對象時需要聲明對象的所有權邻遏,并且確保放棄任何現(xiàn)有價值的所有權。我們可以使用方法訪問來減少在對實例變量進行retain和release時產生的內存管理問題虐骑。
首先我們定義一個類的屬性并聲明它的所有權准验,如下:

@interface Object : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

在屬性聲明之后系統(tǒng)便自動生成兩個訪問方法即“set”和“get”方法。在“get”方法中我們無須對其進行retain或release富弦,僅需返回這個屬性的實例變量即可沟娱。

- (NSNumber *)count {
    return _count;
}

然而在“set”方法中我們需要假定這個新對象的計數(shù)可能會被釋放,所以我們需要retain來取得新對象的所有權腕柜。

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];//retain newCount獲得所有權
    [_count release];
    _count = newCount;
}

如果我們想重新設置這個屬性對象济似,希望通過創(chuàng)建另外一個NSNumber對象來替換,那么我們需要通過釋放來平衡對新對象的內存計數(shù)盏缤。

- (void)resetCount {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    //因為在setCount中已經(jīng)對zero進行了retain
    //那么我們需要對zero進行release砰蠢,保證其內存計數(shù)為1。
    [zero release];
}

還有另外一種就是在使用構造函數(shù)來實例化用于替換的新對象唉铜,我們就不用對其進行release台舱。

- (void)resetCount {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

注意:在Cocoa中一些創(chuàng)建指定對象的方法是通過引用返回的。當我們調用這些方法時,并不是創(chuàng)建該對象竞惋,所以我們沒有它的所有權柜去。最明顯的例子便是NSError。

特別需要注意的是拆宛,當我們使用KVO時嗓奢,像這樣替換對象從而改變變量的值是不正確的。
那么我們有什么地方在設置實例化變量的時候不需要使用方法訪問呢浑厚?答案就是在初始化和dealloc時股耽。
我們在Object初始化的時候,直接實例化了一個新的NSNumber對象钳幅,并將它作為_count物蝙。

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

最后,像Object類這樣存在著count這樣的實例化對象變量敢艰,我們需要實現(xiàn)dealloc方法诬乞。在dealloc中我們通過對count進行release以放棄對其的所有權。

- (void)dealloc {
    [_count release];
    [super dealloc];
}

弱引用避免循環(huán)引用

當我們對一個對象進行retain時便會產生對它的強引用钠导。一個對象只有在它所有的強引用被釋放時才會被釋放丽惭。最常見的就是在兩個對象互相引用時引發(fā)的循環(huán)引用。
下面我們就用一個例子來說明這樣的問題:
Document 存在一個Page對象辈双,而所有的Page對象擁有一個屬性追蹤它所屬的Document對象。如果Document對Page對象進行了強引用并且Page也如法炮制對Document進行了強引用柜砾,這兩個對象都不會被釋放湃望。因為直到Page對象釋放之前Document的引用計數(shù)不能為零,而Page對象在Document對象dealloca之前也不會被釋放痰驱。
這種情況如圖所示:


RetainCycles

為了避免這種問題發(fā)生证芭,我們需要使用弱引用。

A weak reference is a non-owning relationship where the source object does not retain the object to which it has a reference.
弱引用是一個非擁有關系担映,源對象不保留它所引用的對象废士。

然而為了保證界面和圖像的完整性,必須有強引用的存在蝇完。
在Cocoa中存在著這樣一個慣例:一個父對象應該對其子對象保持強引用官硝,而子對象對其父對象是弱引用。
所以在上圖中短蜕,Document對象對Page對象是強引用氢架,而Page對象對Document對象應該是弱引用關系。

Cocoa中弱引用使用的例子比比皆是朋魔,不僅僅是存在于表格數(shù)據(jù)源岖研、視圖,通知觀察者以及代理委托警检。

我們需要謹慎想弱引用對象發(fā)送消息孙援。如果我們對一個已經(jīng)被delloac的對象發(fā)送消息害淤,那么程序將會崩潰。我們必須確保這個對象任然存在有效拓售。弱引用的對象通常知道其他對象對它的弱引用窥摄,就像循環(huán)引用一樣,負責在釋放對象時通知另一個對象邻辉。例如溪王,當我們向通知中心注冊一個對象為觀察者時,通知中心將存儲對該對象的弱引用值骇,并在發(fā)布適當?shù)耐ㄖ獣r向其發(fā)送消息莹菱。當對象被釋放時,我們需要將其注銷到通知中心吱瘩,以防止通知中心將任何進一步的消息發(fā)送到不再存在的對象道伟。同樣,當一個委托對象被釋放時使碾,我們需要通過向另一個對象發(fā)送一個帶有nil參數(shù)的setDelegate:消息來移除委托蜜徽。這些通常是在對象dealloc方法中進行。

避免取消對象的分配

Cocoa所有權政策定義接收的對象通常應該在調用方法的方法體內保持有效票摇,在從當前方法體返回一個接收的對象也不會被釋放拘鞋。我們需要注意的不是一個對象的getter方法中返回的緩存的實例變量或計算值,而是使用這些變量或計算值時是否有效矢门。
但在方法體內也有例外的情況:
(1)當一個對象從基本集合類中被移除

someObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// someObject 已經(jīng)失效了

(2)當父對象被dealloc

someObject = [parent child] ;
[parent release]; 
// someObject 已經(jīng)失效了

我們從另一個對象中檢索對象盆色,然后直接或間接釋放父對象。如果釋放父項導致它被解除分配祟剔,并且父項是該子項的唯一所有者隔躲,那么將同時釋放子項(假設它是在父類dealloc中release而不是自動釋放)。
為了防止在方法體內發(fā)生如下例外情況物延,我們需要對someObject進行retain宣旱。

someObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// someObject 繼續(xù)存在并處理其他事宜
[heisenObject release];

稀缺資源的管理

我們通常不應該在dealloc方法中管理稀缺資源,例如文件描述符叛薯,網(wǎng)絡連接以及緩沖區(qū)或緩存浑吟。特別是,我們不應該設計當我們認為dealloc會被調用就會被調用的類耗溜。因為dealloc方法的調用可能由于錯誤或因為應用程序銷毀而被延遲或回避买置。
相反,如果我們有一個實例管理稀缺資源的類强霎,我們應該設計應用程序忿项,在我們知道什么時候不再需要這些資源時告訴實例去進行“清理”。我們通常會釋放這個實例,然后會釋放dealloc轩触,但不會引發(fā)其他的問題寞酿。

它們包含的對象的集合

當我們添加一個對象到一個集合(數(shù)組、字典或set)里時脱柱,這個集合就會獲取到這個對象的所有權伐弹。當對象從集合中被刪除或集合本身被釋放時,集合將放棄該對象的所有權榨为。所以我們可以這樣:

NSMutableArray *array = [NSMutableArray array];
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}

在這段代碼中惨好,我們沒有使用alloc分配內存,所以也無需調用release随闺。但我們把這段代碼改成下面這樣日川,就需要使用release了。

NSMutableArray *array = [NSMutableArray array];
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    [array addObject:allocedNumber];
    [allocedNumber release];
}

4.自動釋放池塊

自動釋放池塊提供了一種機制矩乐,我們可以放棄對象的所有權龄句,但也不用擔心它立即被釋放掉的可能。通常情況下散罕,我們不需要創(chuàng)建自己的自動釋放池分歇。

關于自動釋放池塊

一個自動釋放池快通過@autoreleasepool被標記。

@autoreleasepool {
}

在自動釋放池塊的末尾欧漱,在塊內收到的自動釋放消息的對象同樣也收到了釋放的消息职抡。
自動釋放池塊也可以嵌套

@autoreleasepool {
    // do something...
    @autoreleasepool {
        // do something...
    }
}

對于一個自動釋放的消息,其對應的釋放消息在已發(fā)送了自動釋放消息的自動釋放池塊末尾被發(fā)送误甚。(>_<)好繞口繁调!

使用本地自動釋放池塊來減少峰值內存占用

許多程序都會創(chuàng)建會被自動釋放的臨時對象。這些對象將添加到程序的內存占用空間直到塊的結束靶草。在許多情況下,允許臨時對象累積到當前事件循環(huán)迭代結束是不會導致過多的開銷的;但是岳遥,在某些情況下奕翔,我們可能會創(chuàng)建大量占用相當多內存占用空間的臨時對象,并且希望更快地進行處理浩蓉。這種情況下派继,我們可以創(chuàng)建我們自己的自動釋放池塊。在塊的末尾捻艳,通常為了減少程序的內存占用而使臨時對象被釋放驾窟。
以下這個例子將會給我們展示如何在For循環(huán)中使用本地自動釋放池塊:

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

在這個For循環(huán)中任何對象在自動釋放池塊的最后被釋放。
在一個自動釋放池塊之后认轨,我們應該把在塊內被自動釋放的任何對象視為“被處置”绅络。
不能再調用這個對象任何方法或者返回給方法調用者。如果我們需要使用autorelease池塊之外的臨時對象,則可以通過向塊中的對象retain恩急,然后在塊之后將其自動釋放杉畜。

– (id)findMatchingObject:(id)anObject {
 
    id match;
    while (match == nil) {
        @autoreleasepool {
 
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
 
            if (match != nil) {
                [match retain]; /* Keep match around. */
            }
        }
    }
 
    return [match autorelease];   /* Let match go and return it. */
}

在以上方法中,處于自動釋放池塊內的match調用了retain衷恭,從而延長了其避免在塊末尾被釋放此叠,以便能在塊之外繼續(xù)調用,并返回給這個方法調用者随珠。

參考資料

Advanced Memory Management Programming Guide

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末灭袁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窗看,更是在濱河造成了極大的恐慌茸歧,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤芦,死亡現(xiàn)場離奇詭異举娩,居然都是意外死亡,警方通過查閱死者的電腦和手機构罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門铜涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遂唧,你說我怎么就攤上這事芙代。” “怎么了盖彭?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵纹烹,是天一觀的道長。 經(jīng)常有香客問我召边,道長铺呵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任隧熙,我火速辦了婚禮片挂,結果婚禮上,老公的妹妹穿的比我還像新娘贞盯。我一直安慰自己音念,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布躏敢。 她就那樣靜靜地躺著闷愤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪件余。 梳的紋絲不亂的頭發(fā)上讥脐,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天遭居,我揣著相機與錄音,去河邊找鬼攘烛。 笑死魏滚,一個胖子當著我的面吹牛,可吹牛的內容都是我干的坟漱。 我是一名探鬼主播鼠次,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芋齿!你這毒婦竟也來了腥寇?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤觅捆,失蹤者是張志新(化名)和其女友劉穎赦役,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栅炒,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡掂摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赢赊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乙漓。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖释移,靈堂內的尸體忽然破棺而出叭披,到底是詐尸還是另有隱情,我是刑警寧澤玩讳,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布涩蜘,位于F島的核電站,受9級特大地震影響熏纯,放射性物質發(fā)生泄漏同诫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一樟澜、第九天 我趴在偏房一處隱蔽的房頂上張望误窖。 院中可真熱鬧,春花似錦往扔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚷堡,卻和暖如春蝗罗,著一層夾襖步出監(jiān)牢的瞬間艇棕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工串塑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沼琉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓桩匪,卻偏偏與公主長得像打瘪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子傻昙,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容