本文會繼續深入學習OC內存管理,內容主要參考iOS高級編程,Objective-C基礎教程,瘋狂iOS講義,是我學習內存管理的筆記
內存管理
1 內存管理的基本概念
1.1 Objective-C中的內存管理
- 手動內存管理和自動釋放池---
MRC
>(Mannul Reference Counting) - 自動內存管理---
ARC
>(Automatic Reference Count) - 自動垃圾回收---
GC
>(Garbage Collection)
由於iOS系統不支持垃圾回收,所以我們在iOS開發中只能使用MRC和ARC來進行內存管理,本文不再介紹Objective-C中的垃圾回收機制,但是此處注意Objective-C中是存在垃圾回收機制的
1.2 內存管理中存在的問題
-
內存泄露
:不再需要的對象沒有釋放引起的問題
:程序的內存占有量不斷增加,最終會被耗盡導致程序崩潰 -
野指針
:沒有進行初始化得指針引起的問題
:浪費內存資源,如果調用程序會出現未知的結果,甚至導致程序崩潰 -
懸空指針
:一個指針指向一個被銷毀的對象引起的問題
:調用懸空指針指向的屬性或者方法時,程序會出現未知的結果,甚至導致程序崩潰 -
僵屍對象
:過度釋放的對象引起的問題
:
2.手動內存管理和自動釋放池---MRC
>(Mannul Reference Counting)
2.1 什么是引用計數(Reference Counting)
引用計數
:Objective-C中引入了引用計數這一機制來跟蹤並處理對象的生命周期,
管理方式
:每個對象都有一個與之關聯的整數,這個整數被稱為引用計數,在Objective-C中,通過不同的方法可以對引用計數進行操作,具體的處理如下表:
對象操作 | Objective-C方法 | 對應的操作結果 |
---|---|---|
生成並持有對象 | alloc , new , copy ,mutableCopy 等方法 |
生成對象並設置引用計數 =1 |
持有對象 | reatain 方法 |
使引用計數 +1 |
釋放對象 | release 方法 |
使引用計數 -1 |
廢棄對象 | dealloc 方法---系統自動調用 |
引用計數 =0 時調用 |
關於delloc
方法:dealloc方法繼承自NSObject
,因此所有的對象都具有此方法,當一個對象的引用計數為0時,也就意味着沒有任何程序需要此對象,系統會回收該對象所占用的內存,在系統銷毀對象之前,會自動調用該對象的dealloc方法來執行一些回收操作,如果該對象還持有其他對象的引用,我們必須重寫dealloc方法來釋放該對象引用的其他對象(通常就是使用該對象的release方法)
引用計數機制回收對象的說明
:如果一個對象的引用計數為0,則表明程序已經不再需要它,這時系統會自動回收該對象所占內存,相反,如果一個對象的引用計數不為0,系統就不應該回收,也不會回收它所占的內存
關於retainCount方法
:Objective-C提供了retainCount方法來返回一個對象當前的引用計數
如何重寫dealloc方法
:
- (void)dealloc {
// 處理該對象的其他引用(通過release方法)
/** 回調父類的dealloc方法 */
[super dealloc];
}
2.2 蘋果如何管理引用計數
- 2.2.1 因為NSObject類的源代碼沒有公開,我們利用Xcode的調試器(lldb)和iOS大概追溯出其實現過程
-
alloc
+alloc +allocWithZone: class_createInstance //此方法可以通過objc4中的runtime/objc-runtime-new.mm確認 calloc // 分配內存塊
-
retainCount
-retainCount __CFDoExternRefOperation // 此函數根據retain,retainCount,release操作進行分發,調用__CFBasicHashXXX方法 CFBasicHashGetCountOfKey
-
retain
-retain __CFDoExternRefOperation CFBasicHashAddValue
-
release
-release __CFDoExternRefOperation CFBasicHashRemoveValue // 當此函數返回0時, -release調用dealloc方法
-
- 2.2.2 由__CFDoExternRefOperation函數以及此函數的調用關系,我們大概推算蘋果大概是使用散列表(引用計數表)來管理引用計數
- 通過引用計數表來管理引用計數的好處:
- 對象用內存塊的分配無須考慮內存塊頭部
- 引用計數表各記錄中存有內存塊的地址,可從各個記錄追溯到各對象的內存塊(在進行內存泄露的檢查時,此條特性具有舉足輕重的作用,即使出現故障導致對象占用的內存塊損壞,但是只要引用計數表沒有被破壞,我們就可以確定各內存塊的位置,這就是設置全局斷點可以查出哪里出現內存泄露的原因)
- 通過引用計數表來管理引用計數的好處:
2.3 內存管理的思考方式
-
自己生成的對象,自己持有
1.1 使用
alloc
new
copy
mutableCopy
創建的對象只能自己持有id obj1 = [[NSObject alloc] init]; id obj2 = [NSObject new]; id obj3 = [NSObject copy]; id obj4 = [NSObject mutableCopy];
1.2 使用以上名稱的開頭的方法也意味着自己生成並持有對象
alloc
NewObjectnew
NewObjectcopy
NewObjectmutableCopy
NewObject -
非自己生成的對象,自己也能持有
2.1 非
alloc
new
copy
mutableCopy
生成的對象,變量obj本身不持有該對象id obj1 = [NSMutableArray array]; id obj2 = [NSDictionary dictionary];
2.2 通過retain方法,非通過
alloc
new
copy
mutableCopy
生成的對象,可以成為自己持有的對象id obj = [NSMutableArray array]; [obj retain];
-
不再需要自己持有的對象時釋放
-
3.1 釋放通過
alloc
new
copy
mutableCopy
生成的對象,一旦不在需要,務必要使用release方法釋放id obj = [[NSObject alloc] init]; [obj release];
-
3.2 用retain方法持有的非自己生成的對象,一旦不再需要,也一定要使用release釋放
id obj = [NSMutableArray array]; [obj retain]; // 通過retain方法持有對象 [obj release]; // 在不需要時也要通過release方法釋放對象
-
3.3 用某個方法生成對象,並將其作為方法的返回值,這時我們該如何處理
-
3.3.1 通過
alloc
new
copy
mutableCopy
或其他符合命名規則的方法生成的對象,只需要原封不動的返回就能讓調用方也持有該對象- (id)allocObject { id obj = [[NSObject alloc] init]; return obj; } - (id)allocObjectWithObject:(id)obj { id object = [obj allocObject]; return object; }
-
3.3.2 如果持有非自己生成的對象,例如[NSMutableArray array]生成的對象,我們要使用autorelease方法釋放
注:
命名規則
:用來取得誰都不持有的對象的方法名不能以alloc
new
copy
mutableCopy
開頭- (id)object { id obj = [NSMutableArray array]; [obj autorelease]; return obj; }
-
3.3.3
autorelease
方法:提供了這樣的功能,使對象在超出指定的生存范圍時自動並正確釋放(調用release方法)
-
-
-
非自己持有的對象無法釋放---注意以下兩點,如果發生這樣的情況會導致程序崩潰
-
4.1 通過
alloc
new
copy
mutableCopy
方法或者通過retain方法持有的對象,一旦不再需要時,必須進行釋放,除此之外其他方法獲得的對象絕對不能釋放,一旦釋放會造成程序崩潰 -
4.2 自己持有的對象釋放后再次釋放,造成僵死對象,引起程序崩潰或在訪問廢棄的對象時崩潰
id obj = [[NSObject alloc] init]; [obj release]; [obj release]; // 再次釋放
-