1. 程式人生 > >黑馬程式設計師——OC基礎——記憶體管理(一)

黑馬程式設計師——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方法,如下

#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]
[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  
      }  
 }