一、記憶體管理黃金法則:
The basic rule to apple is everything thatincreases the reference counter with alloc,[mutable]copy[WithZone:] or retainis in charge of the corresponding [auto]release.
如果一個對象使用了alloc,[mutable] copy,retain,那麼你必須使用相應的release或autonrelease
二、記憶體管理類型分類
基本類型和C語言的類型:如:
int,short,char,struct,enum,union等類型
OC類型:任何繼承於NSObject對象都屬於OC的類型。
我們講的記憶體管理實際上是對OC類型的記憶體管理,它對基礎資料型別 (Elementary Data Type)和C語言的類型並不管用。
三、C和C++記憶體管理的不足
如上,又3個引用指向了這一塊記憶體,任何一個調用了free方法釋放這個記憶體,而其餘的引用在不知道的情況下繼續使用這塊記憶體的時候,就會出現問題。何時由誰去釋放這塊記憶體,這就是C和C++在記憶體管理上的混亂。
四、OC對象在記憶體中的結構
所有的OC類型的對象的結構如下,這個對象的記憶體在包含自己的變數和方法的基礎上,還有一個包含retainCount的引用計數。
每一個OC對象都有一個4個位元組的retainCount的計數器。表示當前對象被引用的計數。如果計數為0,那麼就真正的去釋放這個對象。
規則:
1、Objective-C類中實現了引用計數器,對象知道自己當前被引用的次數
2、最初對象的計數器為1
3、如果需要引用對象,可以給對象發送一個retain訊息,這樣對象的計數器就加1
4、當不需要引用對象了,可以給對象發送release訊息,這樣對象計數器就減1
5、當計數器減到0,自動調用對象的dealloc函數,對象就會釋放記憶體
6、計數器為0的對象不能再使用release和其他方法
五、舉例說明
比如有一個引擎類Engine,有一個汽車類Car,Car裡面有一歌Engine的執行個體變數,一個setter和getter方法。具體如下
#import "Car.h"@implementation Car-(void)setEngine:(Engine*) engine{ _engine=engine;}-(Engine*)engine{ return _engine;}-(void)dealloc{ NSLog(@"Car is dealloc"); [super dealloc];}@end
上面寫的是一個簡單的類,當讓這樣寫是有問題,所以需要一步步的改進。
第一步改進:
先使用它看問題的所在,在main方法裡面如下使用:
//先建立一個引擎 Engine* engine1=[[Engine alloc]init]; [engine1 setID:1]; //在建立一個汽車,設定汽車的引擎 Car* car=[[Car alloc]init];//retainCount=1 [car setEngine:engine1]; /*分析:在這裡,現在有兩個引用指向這個Engine對象,engine1和Car中的_engine,可是這個Engine對象的引用計數還為1,因為在 set方法中,並沒有使用retain。那麼不管是哪個引用調用release,那麼另外一個引用都會指向一塊釋放掉的記憶體,那麼肯定 會發生錯誤。所以需要在set方法中加以改進。*/
第二步改進:
setter方法改進
-(void)setEngine:(Engine*) engine{ _engine=[engine retain];//多了一個引用,retainCount+1}
再在main中使用它
//先建立一個引擎 Engine* engine1=[[Engine alloc]init]; [engine1 setID:1]; //在建立一個汽車,設定汽車的引擎 Car* car=[[Car alloc]init];//retainCount=1 [car setEngine:engine1];//retainCount=2,因為使用了retain,所以retainCount=2, //假設還有一個引擎 Engine* engine2=[[Engine alloc]init]; [engine2 setID:2]; //這個汽車要換一個引擎,自然又要調用settr方法 [car setEngine:engine2]; /*分析:在這裡,汽車換了一個引擎,那麼它的_engine就不在指向engine1的哪個對象的記憶體了,而是換成了engine2,也就是說engine1的哪個對象指向的記憶體的引用只有一個 可是它的retainCount是兩個,這就是問題的所在了。所以仍然需要改進*/
第三步改進:
-(void)setEngine:(Engine*) engine{ [_engine release];//在設定之前,先release,那麼在設定的時候,就會自動將前面的一個引用release掉 _engine=[engine retain];//多了一個引用,retainCount+1}
再在main'中使用
//先建立一個引擎 Engine* engine1=[[Engine alloc]init]; [engine1 setID:1]; //在建立一個汽車,設定汽車的引擎 Car* car=[[Car alloc]init];//retainCount=1 [car setEngine:engine1];//retainCount=2,因為使用了retain,所以retainCount=2, //如果進行了一個誤操作,又設定了一次engine1 [car setEngine:engine1]; /*分析:那麼,又要重新調用一次setter方法,這根本就是無意義的操作,浪費資源,所以要在設定之間加上判斷*/
第四步改進:
-(void)setEngine:(Engine*) engine{ if(_engine!=engine){//判斷是否重複設定 [_engine release];//在設定之前,先release,那麼在設定的時候,就會自動將前面的一個引用release掉 _engine=[engine retain];//多了一個引用,retainCount+1 } }
第五步:
現在setter方法基本沒有問題了,那麼在當我們要釋放掉一個car對象的時候,必須也要釋放它裡面的_engine的引用,所以,要重寫car的dealloc方法。
-(void)dealloc{ [_engine release]; //在釋放car的時候,釋放掉它對engine的引用 [super dealloc];}
這還不是最好的釋放的方法,下面的方法更好
-(void)dealloc{ [_engine setEngine:nil]; //在釋放car的時候,對setEngine設定為nil,它不僅會release掉,並且指向nil,即使誤操作調用也不會出錯。 [super dealloc];}
所以,綜上所述,在setter方法中的最終寫法是
-(void)setEngine:(Engine*) engine{ if(_engine!=engine){//判斷是否重複設定 [_engine release];//在設定之前,先release,那麼在設定的時候,就會自動將前面的一個引用release掉 _engine=[engine retain];//多了一個引用,retainCount+1 } }
然後在dealloc方法中寫法是:
-(void)dealloc{ [_engine setEngine:nil]; //在釋放car的時候,對setEngine設定為nil,它不僅會release掉,並且指向nil,即使誤操作調用也不會出錯。 [super dealloc];}
六、property中的setter文法關鍵字
在property屬性中有三個關鍵字定義關於展開setter方法中的文法,assgin(預設),retain,copy。當然這三個關鍵字是互斥的。
1、assgin展開stter的寫法
-(void)setEngine:(Engine*) engine{ _engine=engine;}
2、retain展開的寫法
-(void)setEngine:(Engine*) engine{ if(_engine!=engine){//判斷是否重複設定 [_engine release];//在設定之前,先release,那麼在設定的時候,就會自動將前面的一個引用release掉 _engine=[engine retain];//多了一個引用,retainCount+1 } }
3、copy展開的寫法
-(void)setEngine:(Engine*) engine{ if(_engine!=engine){//判斷是否重複設定 [_engine release];//在設定之前,先release,那麼在設定的時候,就會自動將前面的一個引用release掉 _engine=[engine copy];//多了一個引用,retainCount+1 } }
對於copy屬性有一點要主要,被定義有copy屬性的對象必須要符合NSCopying協議,並且你還必須實現了-(id)copyWithZone:(NSZone*)zone該方法。
可以看到,使用retain和我們上面舉得例子完全相同,所以我們可以使用property和它的retain代替之前的寫法。