iOS開發日記53-ARC和記憶體管理,ios日記53-arc
今天博主有一個記憶體管理的需求,遇到了一些困痛點,在此和大家分享,希望能夠共同進步.
由於行動裝置的記憶體有限,所以我們需要對記憶體進行嚴格的管理,以避免記憶體泄露造成資源浪費。在OC中,只有對象才屬於記憶體管理範圍,例如int、struce等基礎資料型別 (Elementary Data Type)不存在記憶體管理的概念。在iOS開發中,對記憶體的管理實際上就是對引用計數器的管理。
OC記憶體管理的三種方式自動垃圾收集
在OC2.0中,有一種自動垃圾收集的記憶體管理形式,通過垃圾自動收集,系統能夠自動檢測出對象是否擁有其他的對象,當程式運行期間,不被引用的對象就會自動釋放。
說明:在iOS運行環境中不支援自動垃圾收集,在OS X環境才支援,但是Apple現在不建議使用該方法,而是推薦使用ARC進行替代。
手動引用計數器(MRC)和自動釋放池;引用計數器的概念
顧名思義,引用計數器即一個對象被引用(使用)的次數,每個對象的引用計數器佔用4個位元組
。
如所示,當使用A建立一個對象object的時候,object的RC預設為1,當B也指向object的時候,object的RC+1=2。然後A指標不指向object的時候,object的RC-1=2-1=1。最後當B也不指向object的時候,object的RC-1=1-1=0,此時對象object被銷毀。
說明: 當一個對象被建立的時候,該對象的RC預設為1;當該對象被引用一次,需要調用retain方法,使RC的值+1;當指標失去對該對象的引用,需要調用release方法,使RC的值-1;當RC=0的時候,該對象被系統自動銷毀回收。
手動引用計數器(MRC)
MRC即我們通過人為的方式來控制引用計數器的增減,影響對象RC值得方法有以下幾種:
//Book類的聲明和實現@interface Book:NSObject@end@implementation Book@end//Peron類的聲明和實現@interface Person:NSObject{ Book *_book;}- (void)setBook:(Book *)book;@end@implementation Person- (void)setBook:(Book *)book{ if(_book != book) {//如果新設定的book對象不是之前指向的book對象 [_book release];//使之前對象的RC-1 _book = [book retain];//當前引用的RC+1 }- (void)dealloc //重載dealloc方法銷毀對象{ [_book release];//由於_book控制了Book對象,_book調用release方法使RC-1 [super dealloc];//調用父類的dealloc方法,而且必須放在最後一行}}@end//主函數測試void main(){ Book *b=[Book new]; Person *p = [Person new]; p.book = b; [b release];//b控制了Book對象,b調用release方法使RC-1 [p release];//p控制了Person對象,p調用release方法使RC-1}
通過上面代碼知道,成員變數的設值和取值方法是手動產生的,而且setter方法中成員變數的引用計數器也是手動設定,我們也可以通過@property以及相應關鍵字來由編譯器產生。
關於在MRC中@property關鍵字如下:
1. assign和retain和copy
這幾個關鍵字用於setter方法的記憶體管理,如果使用assign(一般用於非OC對象),那麼將直接執行賦值操作;如果使用retain(一般用於OC對象),那麼將retain新值,release舊值;如果使用copy,那麼將release舊值,copy新值。不顯示使用assign為預設值
。
2. nonatomic和atomic
這兩個關鍵字用於多線程管理,nonatomic的效能高,atomic的效能低。不顯示使用atomic為預設值
。
3.readwrite和readonly
這兩個關鍵字用於說明是否產生setter方法,readwrite將自動產生setter和getter方法,readonly 只產生getter方法。不顯示使用readwrite為預設值
。
4. getter和setter
這兩個關鍵字用於給設值和取值方法另外起一個名字。例如@property(getter=a,setter=b:) int age;相當於取值方法名為a,設值方法名為b:。
如果使用@property屬性,那麼上面代碼可以改為:
//Book類的聲明和實現@interface Book:NSObject@end@implementation Book@end//Peron類的聲明和實現@interface Person:NSObject- (void)dealloc;@property(nonatomic,retain) Book *_book;@end@implementation Person- (void)dealloc //重載dealloc方法銷毀對象{ [_book release];//用於_book控制了Book對象,_book調用release方法使RC-1 [super dealloc];//調用父類的dealloc方法,而且必須放在最後一行}}@end//主函數測試void main(){ Book *b=[Book new]; Person *p = [Person new]; p.book = b; [b release];//b控制了Book對象,b調用release方法使RC-1 [p release];//p控制了Person對象,p調用release方法使RC-1}
循環參考記憶體管理原則
對於兩個類A包含B,B包含A的循環參考情況下,看如下代碼:
//Book1類的聲明和實現@interface Book1:NSObject@property(nonatomic,retain) Book2 *_book2;- (void)dealloc;@end@implementation Book1- (void)dealloc //重載dealloc方法銷毀對象{ [_book2 release];//用於_book控制了Book對象,_book調用release方法使RC-1 [super dealloc];//調用父類的dealloc方法,而且必須放在最後一行}@end//Book2類的聲明和實現@interface Book2:NSObject@property(nonatomic,retain) Book1 *_book1;- (void)dealloc;@end@implementation Book2- (void)dealloc //重載dealloc方法銷毀對象{ [_book1 release];//用於_book控制了Book對象,_book調用release方法使RC-1 [super dealloc];//調用父類的dealloc方法,而且必須放在最後一行}}@end//主函數測試void main(){ Book1 *b1=[[Book1 alloc] init]; Book2 *b2=[[Book2 alloc] init]; b1.book2 = b2; b2.book1 = b1; [b1 release]; [b2 release];}
下面分析主函數代碼,當執行Book1 *b1=[[Book1 alloc] init]後,b1指向Book1。當執行Book2 *b2=[[Book2 alloc] init]後,b2指向Book2。 當執行b1.book2 = b2後,Book1的成員變數_b2指向Book2。當執行b2.book1 = b1後,Book2的成員變數_b1指向Book1。記憶體中具體關係如所示。
此時Book1的引用計數器RC=2,Book2的引用計數器RC=2。
當執行 [b1 release]後,b1釋放對Book1的控制權,此時Book1的引用計數器RC=2-1=1。
當執行[b2 release]後,b2釋放對Book2的控制權,此時Book2的引用計數器RC=2-1=1。
那麼由於仍有指標指向Book1和Book2,這時記憶體中Book1和Book2的關係如黑色橢圓內所示。所以並不會調用dealloc函數,所以Book1和Book2並不會毀銷,這樣就造成了 記憶體泄露
。
對於上面這種情況,只需要在Book1和Book2的@property屬性聲明中
一端使用retain,一端使用assign。即將@property(nonatomic,retain) Book1 *_book1或者@property(nonatomic,retain) Book2 *_book2中的一個retian改為assign。具體原因自己分析。
看下面這種循環參考情況,只能使用assign。
@interface Book:NSObject@property(nonatomic,assign)id instance; //此處必須用assign- (void)dealloc;@end@implementation Book- (void)dealloc //重載dealloc方法銷毀對象{ [_instance release];//用於_book控制了Book對象,_book調用release方法使RC-1 [super dealloc];//調用父類的dealloc方法,而且必須放在最後一行}@endvoid mian(){ Book b1 = [[Book alloc] init]; Book b2 = [[Book alloc] init]; b1.instance = b2; b2.instance = b1; [b1 release]; [b2 release];}
大家可以分析一下,如果@property(nonatomic,assign)id instance; 中將assign換為retain,那麼也將造成記憶體泄露。
Autorelease Pool的使用
顧名思義,autorelease即自動釋放對象,不需要我們手動釋放。從上面代碼我們知道,在主函數中,建立對象obj後,總要手動調用[obj release]方法,這樣無疑使工作量變大,且對我們的技術增長毫無意義。為了減少這種無意義的工作,可以使用Autorelease Pool方式。
Autorelease Pool即自動釋放池,在Autorelease Pool內的對象在建立時只要調用了autorelease方法,那麼在該池子內的對象的最後的release方法的調用將由編譯器完成。
Autorelease Pool的建立有兩種方式:
1.通過@autoreleasepool方法建立,如下:@autoreleasepool{//在大括弧內建立的對象最後不需要手動調用release方法。}
2. 通過NSAutoreleasePool類建立,如下:NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];//此範圍為自動釋放池[pool release];
Autorelease Pool的使用例子如下:
void main(){ @autoreleasepool { Person p = [[[Person alloc] init] autorelease]; //調用autorelease方法 //不需要再調用[p release];方法,超過autoreleasepool範圍會自動調用該方法。 } }
在返回對象的方法中最好使用自動釋放池釋放對象,因為如果將新建立的對象作為傳回值,由於在返回該對象之前並不能釋放該對象,所以可以通過自動釋放池來延遲該對象的釋放。範例程式碼如下:
-(Person *)getNewPerson{ Person p* = [[[Person alloc] init] autorelease]; //do something return p;}或者{ Person p* = [[Person alloc] init] ; //do something return [p autorelease];}
注意點:
自動引用計數器(ARC)
ARC將由編譯器來自動完成對象引用計數器的控制,不需要手動完成。
ARC模式下,建立的新對象通常由以下幾種關鍵字來限定。
在ARC模式下,MRC中的retain、release等方法變的不可用,因為ARC是不需要我們手動管理記憶體的,一切由編譯器完成。
MRC模式下,將一個對象指標賦值給另一個對象指標如下:
Person p1 = [Person new];Person p2 = [Person new];[p2 release]//在p2失去對對象的控制權時需要先releasep2 = p1;//進行賦值操作
但是在ARC模式下,我們完全可以不關心具體怎麼操作,只需要直接進行賦值即可:
Person p1 = [Person new];Person p2 = [Person new];p2 = p1;//進行賦值操作
ARC模式下的循環參考
在ARC模式下,@property屬性關於記憶體管理的修飾符為strong和weak(MRC下的retain和assign不可用),表示聲明為強指標還是弱指標。通常情況下都是使用strong來修飾,但是在循環參考卻不是。
下面這種情況一端使用strong修飾,一端使用weak修飾。如果都使用strong修飾,那麼將造成對象的迴圈保持,造成記憶體泄露。
//Book1類的聲明和實現@interface Book1:NSObject@property(nonatomic,strong) Book2 *_book2;@end@implementation Book1@end//Book2類的聲明和實現@interface Book2:NSObject@property(nonatomic,weak) Book1 *_book1;@end@implementation Book2@end//主函數測試void main(){ Book1 *b1=[[Book1 alloc] init]; Book2 *b2=[[Book2 alloc] init]; b1.book2 = b2; b2.book1 = b1;}
下面這種循環參考情況,只能使用weak。如果使用strong修飾,那麼將造成對象的迴圈保持,造成記憶體泄露。
@interface Book:NSObject@property(nonatomic,weak)id instance; //此處必須用assign@end@implementation Book@endvoid mian(){ Book b1 = [[Book alloc] init]; Book b2 = [[Book alloc] init]; b1.instance = b2; b2.instance = b1;}
注意點:
總結
記憶體管理的本質是對對象引用計數器的操作,理解MRC模式下記憶體管理操作有助於我們對OC記憶體管理的理解。記憶體管理只針對對象而言,注意MRC和ARC下@property屬性關鍵字的選擇,在MRC模式下,OC對象通常使用retain關鍵字,非OC對象使用assign關鍵字,但是循環參考是一個例外,通常需要一端使用assign,一端使用retain;在ARC模式下,OC對象通常使用strong關鍵字,非OC對象使用assign關鍵字,但是循環參考是一個例外,通常需要一端使用strong,一端使用weak