iOS內存管理

概要

Objective-c中的內存管理,也就是引用計數。提供了兩種內存管理機制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting)。

  1. MRC(手動引用計數),手動管理內存。
    MRC模式下,所有的對象都需要手動的添加retain、release代碼 來管理內存。使用MRC,需 要遵守誰創建,誰回收的原則。也就是誰alloc,誰release;誰retain,誰release。當引用計數為0的時候,必須回收,引用計數不為0,不能回收,如果引用計數為0,但是沒有回 收,會造成內存泄露。如果引用計數為0,繼續釋放,會造成野指針。為了避免出現野指針,我們在釋放的時候,會先讓指針置nil。
  2. ARC(自動引用計數),自動管理內存。
    ARC是IOS5推出的新功能,通過ARC,可以自動的管理內存。在ARC模式下,只要沒有強指針(強引用)指向對象,對象就會被釋放。在ARC模式下,不允許使用retain、release、 retainCount等方法。并且,如果使用dealloc方法時,不允許調用[super dealloc]方法。

1.引用計數

看到“引用計數”大家很容易想到“某處有某物多少個”,而Objc采用引用計數的思想是:

  1. 當對象被創建(通過alloc、new、copy、MutableCopy等方法)時,其引用計數初始值為1。
  2. 被其他變量引用而持有時,其引用計數加1。
  3. 其他變量使用結束而釋放時,其引用計數減1。
  4. 當引用計數的值變為0時,表示對象沒有被任何代碼使用,此時對象將被釋放。
    如果1-1所示:


    引用計數的內存管理

示例:

@implementation Test

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"對象被創建,初始引用計數為%ld", self.retainCount);
    }
    return self;
}

- (void)dealloc {
    NSLog(@"對象被銷毀");
    [super dealloc];
}

@end

在main.m中創建Test對象

    NSObject *obj = [[Test alloc]init];
    
    NSObject *obj1 = [obj retain];
    NSLog(@"對象進行retain操作,引用計數:%ld",obj.retainCount);
    
    [obj1 release];
    NSLog(@"retain對象進行release操作,引用計數:%ld",obj.retainCount);
    
    [obj release];

輸出結果為:

2017-11-07 20:24:48.047429+0800 MemoryManger[15557:2996092] 對象被創建,初始引用計數為1
2017-11-07 20:24:48.047605+0800 MemoryManger[15557:2996092] 對象進行retain操作,引用計數:2
2017-11-07 20:24:48.047780+0800 MemoryManger[15557:2996092] retain對象進行release操作,引用計數:1
2017-11-07 20:24:48.047989+0800 MemoryManger[15557:2996092] 對象被銷毀

注:調用或釋放已經銷毀的對象時會導致程序奔潰


15101041233195.jpg

2. autoreleasePool

自動釋放池顧名思義,就是一個池子,可以容納對象,并且自動釋放。為什么需要自動釋放池了?因為當一個對象不再使用時應該將其釋放,但是什么時間釋放?這個時間點很難確定。Objc提供autorelease,當一個對象接收到autorelease消息時,它會被注冊到當前的自動釋放池,當自動釋放池被銷毀時,會給池子里的所有對象發送release消息將其釋放,但在這個時間段內,對象可以正常使用。

它與release的區別如圖1-2所示:


autorelease與release的區別

在Cocoa框架中,相當于程序主循環的NSRunLoop或者在其他程序可運行的地方,對NSAutoreleasePool對象進行生成,持有和廢棄處理。如果1-3所示:


15101072086365.jpg

注:

  1. autorelease不會改變對象的引用計數
  2. 自動釋放池實質上只是在銷毀的時候給池中的所有對象發送release消息,并不能保證對象一定被釋放,如果接受release消息的對象的引用計數仍大于1,對象就無法釋放
  3. 自動釋放池中的對象會集中同一時間釋放,如果操作需要生成的對象較多占用內存空間大,會產生內存不足的現象。典型的例子就是讀入大量圖像的同時改變其尺寸。圖像文件讀入到NSData對象,并從中生成UIImageview對象,改變對象尺寸后生成新的UIImageview對象。這種情況下就會產生大量autorelease對象,可以創建內部的池子來降低內存占用峰值。

3. MRC內存管理基本原則

簡單的說就是:誰創建,誰釋放;誰retain,誰release

  1. 當通過alloc,new,copy,MutableCopy方法創建一個對象時,它的引用計數為1,當不再使用該對象時,應該向對象發送release消息或autorelease消息釋放對象。
  2. 如果獲得對象所有權,就需要保留對象并在操作完成之后釋放,保證retain和release成對出現。

4. ARC

4.1 所有權修飾符

ARC有4中修飾符: __strong, __weak, __autoreleasing, __unsafe_unretained(為兼容iOS5以下版本的產物,可以理解成MRC下的weak)

  1. __strong:表示對對象的“強引用”,無修飾符情況下的默認值。
  2. __weak:弱引用,不持有所指向對象的所有權.
    注:
    1). 互相強引用,很容易引起循環引用問題。__weak修飾符的變量不持有對象,所以在超出其變量作用域時,對象即被釋放。故在可能發生循環引用的類成員變量改用__weak修飾,即可避免。如圖所示:



    __weak修飾符避免循環引用

2). 弱引用指向的對象被釋放后,引用本身會置nil,避免野指針。

    id __weak obj0 = nil;
    {
        id  obj1 = [[NSObject alloc]init]; // obj1變量為強引用
        obj0 = obj1;                       // objc0變量持有對象的弱引用
        NSLog(@"A:%@",obj0);               // 輸出objc0變量持有的弱引用的對象
    }
    /*
     * 1. objc1變量超出其作用域,強引用失效,自動釋放自己持有的對象
     * 2. 對象無持有者,對象被銷毀
     * 3. 持有改對象的弱引用obj0變量的弱引用失效,賦值為nil
     */
    NSLog(@"B:%@",obj0);

輸出結果:

2017-11-08 13:52:01.008357+0800 MemoryManger[19017:3772013] A:<NSObject: 0x600000010070>
2017-11-08 13:52:01.008508+0800 MemoryManger[19017:3772013] B:(null)

3). __autoreleasing:自動釋放對象的引用,一般用于傳遞參數。
比如解析數據方法:

+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

調用時會發現如圖提示

編譯器會將源代碼轉化成以下形式:

 NSError *error = nil;
 NSError * __autoreleasing tmp = error;
 id value = [NSJSONSerialization JSONObjectWithData:self options:kNilOptions error:&tmp];

5. 屬性的內存管理

屬性的修飾詞可以分為三類:

  1. 原子性:nonatomic,atomic
  • nonatomic:非原子性訪問,不加同步,多線程并發訪問會提高性能。
  • atomic:為原子操作,是防止在寫未完成的時候被另外一個線程讀取,造成數據 錯誤。這種機制是耗費系統資源。
  1. 讀寫屬性:readwrite,readonly
  • readwrite:是默認屬性,生成getter和setter方法
  • readonly:只生成getter方法
  1. 內存屬性:assign、strong/retain 、weak、copy
  • assign:直接賦值,索引計數不改變,適用于基本數據類型
  • strong/retain:release舊值,retain新值(用于OC對象)
    使用set方法賦值時,實質上是會先保留新值,再釋放舊值,再設置新值,避免新舊值一樣時導致對象被釋放的的問題。
MRC寫法:
- (void)setTitle:(NSString *)title {
        [title retain];
        [_title release];
    _title = title;
}
ARC寫法:
- (void)setTitle:(NSString *)title {
    _title = title;
}

  • copy : release舊值,copy新值。一般用來修飾String、Dict、Array等需要保護其封裝性的對象,尤其是在其內容可變的情況下,因此會拷貝(深拷貝)一份內容給屬性使用,避免可能造成的對源內容進行改動。

  • weak:對對象的一種弱引用,不保留新值,也不釋放舊值,只設置新值。


希望本文能拋磚引玉,幫助大家對內存管理有更多的理解。如有不當之處,希望大家能多多指點我。


References:
《Objective-C高級編程》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容