2012-04-30

關於iOS ARC三兩事

在iOS5引入ARC之前,舊的記憶體管理機制倒是很直覺。譬如每一個NSObject都擁有一個名為"retainCount"的變量,它表示該Object有多少個引用:

example 1:
NSObject *obj = [NSObject alloc]; //alloc會將obj的retainCount+1
[obj retain]; //retainCount++ 通常在Object賦值之後這樣做,代表它多了一個引用。
[obj release];//retainCount-- 通常在使用完該Object的時候這樣做
而當retainCount為0時,運行時環境會通過調用[obj dealloc] 來釋放obj佔用的記憶體。

example 2:
Test *t1 = [[Test alloc] initWithNum:12];//創建了一個物件,t1是該對象的引用,
//由於調用了alloc此時retainCount為1
Test *t2 = t1;//此時t2也要使用該物件
[t2 retain]; //t2要使用物件 就必須要retain,此時retainCount為2
[t1 release];//t1這時不用該物件了 就release了,也就是放棄了物件的使用權,
//此時retainCount為1
[t2 release];//t2使用完了 就release 此時retainCount為0,立刻會調用dealloc來釋放內存

而關於autorelease的用法:
iOS在global的空間中其實維護了一個型別為NSMutableArray的autoreleasey pool物件。可以利用[[obj alloc] autorelease];
這個函式將把obj加入到autorelease pool的Aarray中(此時obj的retain會+1)

autorelease pool會在系統閒置時,會給在Array中所有的Object發送release(retainCount-1)並從Array中移除物件,如果該物件 原本的retainCount為1,則會因pool呼叫release使retainCount為0,進而呼叫Object的dealloc,以此來實現 Memory釋放。
而obj在執行autorealse後,請不要再次執行retain,使得obj的reatinCount為2,在之後autorelease pool發送release時,obj的retainCount會為1,則會發生memory leak問題。

在iOS5引入ARC後,只要使用alloc來創建物件,則它的刪除與釋放就可以交於系統來處理 (retain release 交由compiler判斷)。ARC多了兩個修飾詞strong,weak來描述物件的指標:

strong :
(擁有該物件,即負責該物件的生命週期)
strong所代表的就像是我們所熟悉的retain-->確保這個成員變數在它的母物件尚未被釋放前都是依然有效的

example 3:
@interface DoctorEntity : NSObject
@property (nonatomic, strong) NSString * docName;
+(DoctorEntity*) doctorWithName:(NSString*) docName;
@end

@implementation DoctorEntity
@synthesize docName = _docName;
+(DoctorEntity*) doctorWithName:(NSString*) docName
{
    DoctorEntity* doctor = [DoctorEntity alloc]; //doctor.docName retainCount = 0;
    doctor.docName = docName; //doctor.docName retainCount = 2
    //與外部生成docName的共享物件
 
    return doctor;
}
在DoctorEntity被解構時,會將doctor.name設為nil 一併釋放

但如果強制將doctor.name設為nil
譬如
example 4:
+(DoctorEntity*) doctorWithName:(NSString*) docName
{
    DoctorEntity* doctor = [DoctorEntity alloc]; //doctor.docName retainCount = 0;
    doctor.docName = docName; //doctor.docName retainCount = 2
    //與外部生成docName的共享物件
    doctor.docName = nil;
    //不管doctor.docName retainCount為多少,retainCount一律清為0
    //進而釋放該物件在heap的記憶體
    return doctor;
}
@end

weak:
(不負責其生命周期,純粹使用指標)
weak修飾的指標純粹就是一個指向在Heap物件的指標,但是在物件被釋放後會自動設為nil。

如果不宣告,iOS5預設值都是使用__strong來定義變數,由編譯器幫你在適當的地方加上retain和release(與 autorelease),如果要改變預設指標的修飾詞的話,可以在變數前加上__weak。就像是之前所說的weak,也就是說如果之後沒有其它人再參 照它,它就會被設定成nil。

因此一般的堆疊變數(local variables)如果你把它設定成__weak,而且之後也沒有其它的strong pointer指向它,則它就是一被生成就會自動被回收。

example 5:
__weak NSString name = @"My name is Franky";
NSLog(@"%@",name);
執行的結果會顯示為空白(因為這時name已經被設為nil了)

ARC大大簡化了原本使用MRC所需要的程式碼,但凡事有ㄧ好就沒兩好。ARC在程式有使用Block時,如果不小心,很容易引起retain cycle。
關於這個,又可以再寫一篇文章來論述了。