MRC期間的記憶體管理方式

weixin_33912246發表於2018-04-11

MRC引用計數規則

記憶體管理的範圍:任何繼承了NSObject的物件,對基本資料型別無效(系統會自動回收)
相關名詞:

  • 記憶體洩漏:程式未能釋放已經不在使用的記憶體
  • 殭屍物件:物件被銷燬,不能再使用
  • 野指標(iOS):指標指向的物件已經被銷燬,指向殭屍物件的指標,準確的說iOS中的野指標應該叫懸垂指標,對於野指標,要及時將其賦值為nil,成為空指標
  • 空指標:沒有指向任何物件的指標

記憶體管理的思考方式:

  • 自己生成的物件,自己持有
  • 非自己生成的物件,也能持有
  • 不再需要自己持有的物件時,釋放
  • 非自己持有的物件無法釋放
操作 方法 思考方式
生成物件 alloc/new/copy/mutableCopy 自己生成,自己持有
持有物件 retain 非自己生成,也能持有
釋放物件 release 不再需要時,釋放
廢棄物件 dealloc

相關方法

  • alloc/new/copy/mutableCopy:生成物件,引用計數初值為1
  • retain:引用計數+1,並返回+1後的當前例項物件
  • release:引用計數-1,並沒有釋放記憶體
  • dealloc:例項物件銷燬前,自動呼叫;引用計數為0,自動呼叫

release與dealloc的區別:release釋放的是對該物件的所有權,dealloc釋放的是該物件佔用的記憶體

自己生成,自己持有(alloc/new/copy/mutableCopy)

//生成並持有物件
id obj1 = [[NSObject alloc] init];
id obj2 = [NSObject new]; 
NSString *str = @"123";
NSString *str2 = [str copy];    //生成並持有str的副本

NSMutableArray *array1 = [NSMutableArray alloc]init];
NSMutableArray *array2 = [array1 mutableCopy];     //生成並持有array1的副本

copy方法利用基於NSCopying方法約定,由各類實現的copyWithZone:方法生成並持有物件的副本。
mutableCopy方法利用基於NSMutableCopying:方法約定,由各類實現的mutableCopyWithZone:生成並持有物件的副本。
區別:copy生成不可變更的物件,mutableCopy生成可變更的物件

注:以alloc/new/copy/mutableCopy開頭,使用駝峰命名法的方法,也是自己生成並持有物件

非自己生成,也能持有
通過retain方法,讓不是自己生成的物件與使用alloc/new/copy/mutableCopy方法生成的物件一樣都能被持有。

//取得,但不持有物件
NSArray *array = [NSArray array];
//持有物件
[array retain]

不再需要的物件,釋放
alloc/new/copy/mutableCopy生成並持有的物件,或者retain方法持有的物件,一旦不需要,無比要使用release進行釋放。

{
    //取得,但不持有物件
    id obj = [NSMutableArray array];
    //持有物件
    [obj retain];
    //釋放物件
    [obj release];
}

注:
記憶體中的常量物件(類物件、常量字串物件等)的記憶體分配空間與其他物件不同,沒有引用計數機制,永遠不能釋放這些物件,獲取的retainCount結果返回為NSUIntegerMax(最大的無符號整數)

非自己持有的物件無法釋放
釋放後,再次釋放

id obj = [NSObject alloc]init];
[obj release];
[obj release];

取得物件存在,但不持有時釋放

id obj = [NSObject object];
[obj release];

這些都會導致程式的崩潰

MRC下autorelease的使用

autorelease的具體使用方法:

  1. 生成並持有NSAutoreleasePool物件
  2. 呼叫已分配物件的autorelease例項方法
  3. 廢棄NSAutoreleasePool物件
while(...) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    /*
        在此進行一系列操作,呼叫臨時物件的autorelease方法
    */
    [pool drain]; // 廢棄pool
}

注:

  • 需要長時間執行的程式碼段或大量使用臨時物件的程式碼可以通過autoreleasepoool提高記憶體利用率
  • 可以給例項多次傳送retain,相應的也可以給例項多次傳送autorelease,只要autorelease和retain要成對傳送

release與autorelease的區別
release:呼叫後,立即釋放物件
autorelease:物件在超出指定的生存範圍是能夠自動並正確的釋放(呼叫release方法)
過程[obj autorelease]不立即釋放變數obj所指的物件,而是註冊到autoreleasepool中,pool結束時自動呼叫release

  1. 以alloc為頭,使用駝峰命名法的方法,如何實現自己生成,自己持有的?
- (id)allocObject 
{
    //obj生成並持有物件
    id obj = [NSObject alloc]init];
    return obj;
} 

id obj = [NSObject allocObject];
  1. 如何實現[NSArray array]這種取得物件存在,但並不持有的?
- (id)object
{
    //obj生成並持有物件
    id obj = [[NSObject alloc]init];
    //obj註冊到autoreleasepool中,這裡的autoreleasepool一般都是main函式最開始生成的自動釋放池
    [obj autorelease];
    return obj;
}

//取得物件,並不持有
id obj = [NSObject object];
[obj retain];   //持有物件

MRC中要重寫的方法

  1. dealloc:徹底釋放一個物件,還需要釋放該物件所持有的所有的物件的所有權
- (void)dealloc
{
    //所有持有的物件呼叫release方法,釋放所有權
    [super dealloc];    //一定要呼叫父類的dealloc,記憶體的釋放才會從子類一直上升到NSObject,物件才會徹底被釋放
} 
  1. setter方法

基本型別:

- (void)setObj:(NSUInteger)obj
{
    _obj = obj;
}

物件型別:

//以下寫法引數obj與_obj是同一物件時,會出問題
- (void)setObj:(id)obj {
    //obj與_obj都只指向同一物件,但obj不持有物件,這時release後物件
    //引用計數為0,物件被銷燬,然後呼叫retain會出問題
    [_obj release];
    _obj = [obj retain];
}

//兩種更安全的寫法
- (void)setObj:(id)obj
{   
    //新物件所有權+1
    [obj retain];
    //舊物件所有權釋放
    [_obj release];
    //將新物件賦給_obj
    _obj = obj;
}

- (void)setObj:(id)obj
{
    if(_obj != obj)
    {
        //釋放舊物件所有權
        [_obj release];
        //賦給_obj所有權+1後的新物件
        _obj = [obj retain];
    }
}

重寫過後的setter方法與例項變數直接賦值的比較:

id obj = [[NSObject alloc]init];
self.obj = obj;     //重寫過setter方法後,所有權會+1
_obj = obj;         //例項變數直接賦值,所有權不變

3、getter方法
getter方法不屬於alloc/new/copy/mutableCopy的自己生成自己持有,與[NSArray array]一樣,都是取得非自己生成物件但不持有

- (id)obj
{
    id tmp = [obj retain];
    return [tmp autorelease];   //註冊到autoreleasepool中,實現取得非自己生成物件但不持有,這裡的autoreleasepool通常都是main函式裡最開始生成的那個
}

int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Object *obj = [Object obj];
    [pool drain];
}

總結:

  • 自己生成物件自己持有,只適用於以alloc/new/copy/mutableCopy開頭的方法。
  • MRC的引用計數規則下,持有方法與釋放方法一定要成對使用,確保物件被正確釋放
  • 其他方式獲得的物件,都屬於取得非自己生成的物件,所以需要呼叫autorelease方法放入到autoreleasepool中。

參考資料:

  • Objective-C程式設計全解
  • Objective-C高階程式設計iOS與OS X多執行緒和記憶體管理

相關文章