黑馬程式設計師——OC基礎——記憶體管理(一)
一,為什麼要進行記憶體管理
1,由於移動裝置的記憶體有限,所以每個APP所佔的記憶體也是有限制的,當APP所佔用的記憶體較多時,系統就會發出警告,這時就需要回收一些不需要繼續使用的記憶體空間,比如回收一些不再使用的物件和變數等。
任何繼承NSObject的物件,對其他的基本資料型別無效
本質原因是因為物件和其他資料型別在記憶體中的儲存空間不一樣,其他區域性變數主要放於棧中,而物件儲存在堆中,當代碼塊結束時這個程式碼塊中涉及的所有區域性變數會被回收,指向物件的指標也被回收,此時物件已經沒有指標指向,但依然存在於記憶體中,造成記憶體洩露。
2,記憶體管理的黃金法則:
蘋果官方文件原話: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,[multable]copy,retain,那麼你必須使用相應的release或者autonrelease
3,記憶體管理型別分類
基本型別和C語言的型別:
int,short,char,struct,enum,union等型別
OC型別:任何繼承於NSObject物件都屬於OC的型別
記憶體管理實際上是對OC型別的記憶體管理,它對基本資料型別和C語言的型別不管用
4,OC物件在記憶體中的結構
所有的OC型別的物件結構中都包含一個retainCount的引用計數
每一個OC物件都有一個4個位元組的retainCount的計數器,表示當前物件被引用的計數。如果計數器為0,那麼就釋放這個物件。
規則:
1>OC類中實現了引用計數器,物件知道自己當前被引用的次數
2>物件建立時計數器為1.
3>如果需要引用物件,可以給物件傳送一個retain訊息,物件計數器+1
4>使用release可以使物件計數器-1.
5>當計數器為0時,自動呼叫物件的dealloc函式,物件就會釋放記憶體
6>計數器為0的物件不能再使用release和其他方法
5,舉例說明:
比如有一個引擎類Engine,有一個汽車類Car,Car裡面有一個Engine的例項變數,一個setter和getter方法,如下
在main方法裡:#import "Car.h" @implementation Car -(void)setEngine:(Engine *)engine { _engine = engine; } - (Engine *)engine { return _engine; } - (void)dealloc { NSLog(@"Car is dealloc"); [super dealloc]; } @end
Engine *engine1 = [[Engine alloc]init];
[engine1 setID:1];
//在建立一個汽車,設定汽車的引擎
Car *car = [[Car alloc]init]
[car setEngine:engine1];
分析:現在有兩個引用指向這個Engine物件,engine1和Car中的_engine,可是這個Engine物件的引用計數還未1,因為set方法中並沒有使用retain。那麼不管是那個引用呼叫release,那麼林外一個都會引用都會指向一塊釋放掉的記憶體,那麼肯定會發生錯誤。
setter方法改進:
- (void)setEngine:(Engine *)engine
{
_engine = [engine retain];
}
再在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
}
//先建立一個引擎
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];
}
所以,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
}
}