01 概述
1.內存中有五大區域,為什么只需要關心堆區?
1)堆區存的是對象,對象占的區域可能會很大
2)堆區的對象不會自動釋放,需要手動管理,如果不進行管理,那么會直到程序結束的時候才會釋放
2.什么時候釋放堆區的對象
1)當這個OC對象沒有"人"使用的時候,可以釋放
2)當這個OC對象有"人"使用的時候,千萬不要釋放
注意:"人"表示對象的使用者
3.如何判斷一個OC對象是否有人使用
1.每一個OC對象都有一個引用計數器(retainCount),表示對象的使用者的個數
2.每當創建一個新的對象,retainCount值默認是1
3.每當多一個人要使用對象的時候,retainCount+1
4.每當少一個人要使用對象的時候,retainCount-1
5.當retainCount=0的時候,系統會立即回收該對象,同時給對象發送dealloc消息
4.程序員如何控制retainCount值?
1.向對象發送release消息,讓對象的retainCount-1
2.向對象發送retain消息,讓對象的retainCount+1
3.向隊形發送retainCount消息,獲得對象的retainCount值
4.所謂的消息,其實就是對象的方法
所有和內存管理相關的成員變量以及方法,都是定義在NSObject類中的
5.內存管理的分類
1.MRC,手動引用計數,程序員需要手動管理對象的retainCount值,
2.ARC,自動引用計數,編譯器幫我們管理對象的retainCount值
3.ARC和MRC的關系1.ARC是基于MRC的
2.ARC是編譯器特性,編譯器在編譯的時候,會在合適的位置,自動插入內存管理的代碼
3.ARC是2011年iOS5出現,是程序員的一大福音,省去了很多繁瑣的內存管理的工作
4.從Xcode6開始,新建的項目默認都是ARC的
6.MRC是ARC的底層原理
只有理解MRC才能更好的理解內存管理的原理
02 MRC程序
1.如何關閉ARC機制,把項目設置為MRC
在項目信息頁面,選擇BuildSettings,搜索auto,找到"A...R...C...",默認是YES,改成NO即可.
注意:如果在ARC環境下,不能使用內存管理的方法,否則編譯器會報錯.
2.為什么我們重寫dealloc方法的時候,需要在最后調用父類的dealloc方法?
重寫init方法的時候
1.父類的init方法實現默認有一些初始化對象時必須的操作
2.需要在父類init方法的基礎之上增加一些操作,這個時候需要先調用父類中init方法實現
重寫dealloc方法的時候
1.父類的dealloc方法實現中默認有一些對象釋放的是否必須的操作,一旦調用了NSObject類中dealloc方法的實現,那么對象就會被徹底的釋放
2.我們在對象徹底釋放之前可能還需要對對象做一些事情,比如說需要先釋放當前對象中特有一些內容,在完成這些操作之后,再調用父類中dealloc方法的實現
3.注意點:
1.重寫dealloc方法的時候,必須在最后調用父類中dealloc方法的實現(MRC)
2.dealloc方法不需要手動調用,當對象銷毀的時候系統會自動調用該對象的dealloc方法,千萬不要手動調用
3.對象調用release方法之后就會銷毀嗎?
不一定,release方法只會讓當前對象的引用計數器-1,和銷毀對象之間沒有什么必然聯系
在MRC中判斷對象是否銷毀只有一個原則,就是對象的引用計數器是否為0
03 內存管理的原則
1.保持平衡
1.有new\alloc,就必須要有release
2.有retain,就必須要有release
3.誰new\alloc,誰release
4.誰retain,誰release
// 2.使用哪個指針給對象發送了new/alloc消息,就需要通過這個指針發送release
// 3.使用哪個指針個對象發送了retain消息,就需要通過這個指針發送release
Person *p1 = [[Person alloc] init]; //1
//[p1 release];
//Person *p2 = p1;
//[p2 retain];
// retain內部把當前對象的retainCount+1,然后返回當前對象
Person *p2 = [p1 retain]; // 2
[p1 release] // 1
[p2 release] // 0 -> 由系統調用dealloc方法,釋放對象
//
//Person *p2 = [p1 retain];
//Person *p3 = p1;
//
//[p1 release];
//[p2 release];
04 野指針與僵尸對象
在OC中
1.對象的回收
1.一旦對象回收,說明對象在內存中占有的堆區空間,可以分配給別人使用(不代表空間被刪掉,也不代表立刻分配給被人使用)
2.但是在系統把該空間分配給其他人使用之前,對象還存在(對象中的數據還存在)
2.僵尸對象
對象已經被回收了,但是對象的數據還存在內存中,這樣的對象叫做僵尸對象
注意點:
1.當僵尸對象占用的空間,系統沒有分配給其他人使用的時候,這個對象其實還存在,還可以訪問
2.當僵尸對象占用的空間,系統已經分配給其他人使用的時候,這個對象不存在,不可以訪問
3.嚴格意義上來說,對象一旦成為了僵尸對象,無論如何不能訪問
4.僵尸對象無法復活(不應該調用僵尸對象的方法)
Xcode中開啟僵尸對象檢測
默認沒有開啟,避免浪費性能
Edit Scheme -> Diagnostics -> 勾選Enable Zombie Object
3.野指針
指向被回收對象的指針,叫做野指針
指向堆區僵尸對象的棧區指針變量,叫做野指針
4.如何防止野指針錯誤
Person *p1 = [[Person alloc] init]; // 1
[p1 release] // 0
// p1是一個野指針,p1指向的堆區對象是僵尸對象
p1 = nil;
[nil sayHi];
05 單個對象的內存管理
int main() {
Person *p1 = [Person new];
}
1.如何避免內存泄漏
1.retain和release一定要匹配
2.不要讓指針隨便指向nil
06 多個對象的內存管理之一
1.當對象A中擁有一個成員變量是對象B,如果A沒有釋放,那么B就不能是釋放.因為B作為A的成員變量,說明A隨時都有可能使用B,那么必須當A釋放的時候,才能把B釋放.
演化步驟:
1.創建對象A, 創建對象B
2.把B對象設置給A對象,
出現的問題:當B對象釋放之后,A對象就無法再使用B對象
解決方案:
3.在把B對象設置給A的時候,讓B對象的引用計數器+1,說明A對象擁有B對象
出現的問題:當A對象釋放之后,B對象無法釋放
解決方案:
4.當A對象銷毀的時候,也就是在A對象的dealloc方法中,先讓B對象的引用計數器-1,再釋放A對象
07 多個對象的內存管理之二
出現的問題:
如果給A類對象a中的B類型的成員變量更換了一個值,從b對象換成了b1,此時在a對象銷毀的時候,釋放的是b1,而無法釋放b,導致b內存泄漏
解決方案:
5.在B類型成員變量的set方法中,先release舊對象b,再retain新對象b1
08 回收分析
兩個A類型的不同對象a和a1,同時擁有一個B類型的成員和變量b,沒有出現任何問題
09 多個對象的內存管理之三
出現的問題:
如果給A類對象a中的B類型成員變量重復賦同一個值,之前是b,再賦一次b
因為在B類型的成員變量set方法中,是先release再retain,此時有可能導致b對象在release之后已經釋放,變成僵尸對象,再進行retain操作會造成野指針錯誤
解決方案:
6.在B類型成員變量的set方法中,需要進行判斷,如果是相同對象什么都不做,如果不是相同對象,先release舊對象,再retain新對象.
- (void)setB:(B *)b {
if (_b != b) {
[_b release];
_b = [b retain];
}
}
- (void)dealloc {
[_b release];
[super dealloc];
}
10 當屬性的類型是OC對象的時候,setter方法的寫法
注意:
當成員變量是OC對象的時候,它的set方法才需要按照以上總結的內容來寫.
當成員變量是基本數據類型的時候,直接賦值即可.
11 @property參數之與多線程相關的
多線程相關
- atomic(默認值) 安全的
- nonatomic 不安全的
12 @property參數之與生成setter方法實現相關的
在MRC下和內存管理相關的參數
assign(默認)
retain
如果成員變量是OC對象,使用retain
如果成員變量是非OC對象,使用assign
13 @property其他參數
1.讀寫相關的
1.readwrite(默認),可讀可寫,會同時生產getter和setter
2.readonly,只讀,只會生產getter,而不會生成setter
2.指定setter和getter名字
注意:
1.如果需要設置setter的名字,一定要加:.但是無論如何都不要更改setter的名字
2.如果成員變量是BOOL類型,getter一般以is開頭,增加可讀性
3.只是改變了setter和getter的名字,并沒有改變內部實現
14 @property參數使用注意
1.在使用@property的時候,必須要加參數
1.每種類型的參數,同時只能存在一個
2.至少需要兩類:線程相關的,內存管理相關的
規范寫法:@property (nonatomic, retain) NSString *name;
2.如果在MRC環境下
當使用@property定義屬性的時候,如果使用reatin,只會在setter中生成標準的MRC內存管理代碼
但是dealloc中對該成員變量,并沒有release,需要我們自己來做