iOS內存管理

1.引用計數式內存管理的思考方式

  • 本身生成的對象,本身持有
  • 非本身生成的對象,本身也能持有
  • 不在須要本身持有的對象時釋放
  • 非本身持有的對象沒法釋放

2.alloc方法

+ alloc
+ allocWithZone:
class_creatInstance
calloc

調用alloc方法首先調用allocWithZone:類方法,而後調用class_creatInstance函數,最後調用calloc來分配內存塊。安全

3.retainCount/retain/release 方法

- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
-  retain
__CFDoExternRefOperation
CFBasicHashAddValue
- retainCount
__CFDoExternRefOperation
CFBasicHashRemoveValue //CFBasicHashRemoveValue 爲0時,-release調用dealloc

各個方法都經過同一個__CFDoExternRefOperation函數,調用一系列名稱類似的函數。而且從函數名看出蘋果採用散列表(引用計數表)來管理引用計數,表鍵值爲內存塊地址的散列值。然而GNUStep將引用計數保存在對象佔用內存塊頭部的變量中(objc_layout這個結構體中)。多線程

  • 內存塊頭部管理引用計數的好處:框架

    1. 少許代碼皆可完成
    2. 可以統一管理引用計數內存塊與對象內存塊。
  • 引用技術表管理引用計數的好處:
    1. 對象內存快的分配無需考慮內存塊頭部函數

    1. 引用計數表各記錄中存有內存塊地址,可從各個記錄追溯到各個內存塊。
第二條特徵在調試時很重要,即便出現故障致使對象佔用的內存塊損壞,但只要引用計數表沒有被損壞,就可以確認各個內存塊的地址

4.autorelease方法

  1. NSAutoreleasePool是經過以AutoreleasePoolPage爲結點的雙向鏈表來實現的。AutoreleasePoolPage是一個C++實現的類,類結構如圖:
    圖例oop

    • magic 用來校驗 AutoreleasePoolPage 的結構是否完整。
    • next 指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin()。
    • thread 指向當前線程。
    • parent 指向父結點,第一個結點的 parent 值爲 nil。
    • child 指向子結點,最後一個結點的 child 值爲 nil。
    • depth 表明深度,從 0 開始,日後遞增 1。
    • hiwat 表明 high water mark。
  2. AutoreleasePoolPage每一個對象會開闢4096字節內存(也就是虛擬內存一頁的大小),除了實例變量所佔空間,剩下的空間所有用來儲存autorelease對象的地址內存結構如圖:
    圖片描述
  3. 在Cocoa框架中,NSRunloop每次循環過程當中NSAutoreleasePool對象被生成或廢棄。在大量產生autorelease對象時,只要不廢棄NSAutoreleasePool那麼生成的對象就不能被釋放,在此狀況下有時會產生內存不足的現象,所以有必要適當的生成,持有和廢棄NSAutoreleasePool。一般在使用Objective-C,不管調用哪個對象的autorelease/retain方法,實現上都是調用NSObject類的autorelease/retain實例方法,可是對於NSAutoreleasePool類,autorelease/retain實例方法已被重寫,所以運行時會出錯(exception)。autorelease實際上把對象的釋放時機交給NSAutoreleasePool管理,使用方法以下:spa

    • 生成並持有NSAutoreleasePool對象。線程

      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 等同於 objc_autoreleasePoolPush()
    • 調用已分配對象的autorelease實例方法。指針

      id obj = [[NSObject alloc] init];
      [obj autorelease]; // 等同於 objc_autorelease()obj
    • 廢棄NSAutoreleasPool對象(自動調用分配對象的release)。調試

      [pool drain]; // 等同於 objc_autoreleasePoolPop(pool)

5.ARC規則

ARC(Automatic Reference Counting)是編譯階段自動作了retain/release,原先須要手動添加處理引用計數的代碼能夠自動地由編譯器完成,但實際上只有編譯器是沒法徹底勝任的,在此基礎上還須要Objective-C運行時庫協助。同一程序中按文件單位能夠選擇ARC有效和無效。Core Foundation中的malloc()或者free()等,仍是須要本身手動進行內存管理。ARC規則以下code

  1. 不能使用retain/release/retainCount/autorelease。
  2. 不能使用NSAllocateObject/NSDeallocateObject。
  3. 必須遵照內存管理的方法命名規則。
  4. 不要顯式調用dealloc。
  5. 使用@autoreleasepool塊代替NSAutoreleasePool。
  6. 不能使用區域。
    雖然說ARC有效時,不能使用區域(NSZone)。無論ARC是否有效,區域在如今的運行時系統(編譯器宏__OBJC2__)中已單純的被忽略
  7. 對象型變量不能做爲C語言結構體的成員。

    struct Data {
        NSMutableArray *array; /* error: ARC forbids Objective-C objs in structs or unions  NSMutableArray *array */
    }
    /* 要把對象型白能量加入到結構體成員中時,可強制轉換爲void *(見下一條規則)或是附加「__unsafe_unreatained」修飾符。可是附有「__unsafe_unreatained」修飾的變量不屬於編譯器的內存管理對象,可能形成內存泄露或者崩潰。*/
     struct Data {
        NSMutableArray __unsafe_unreatained *array; /
    }
  8. 顯式轉換「id」 和 「void」。

    • 在MRC下"id"和"void *"能夠強制轉換,但在ARC下編譯器報錯,代碼以下:

      id obj = [[NSObject alloc] init];
      void *p = obj;   // ARC編譯報錯
      
      id o = p;  // ARC編譯報錯
      [o release];
    • ARC狀況下要用 "bridge轉換",可是ARC不推薦此用法,這種轉換常常用在Objective-C和Core Foundation對象之間的相互變換。

      • __bridge轉換:只作類型轉換,在不改變對象的引用計數,可能會形成懸垂指針而致使程序崩潰。
      • __bridge_retained轉換:可以使目標變量也持有賦值對象。對象引用計數加1。如下是ARC和MRC等價的代碼

        // ARC
        id obj = [[NSObject alloc] init];
        void *p = (__bridge_retained void *)obj;
        // 等效 MRC 代碼
        id obj = [NSObject alloc] init];
        void *p = (void *)obj;
        [(id)p retain];
      • __bridge_transfer轉換:被轉換的變量所持有的對象在賦值給目標變量後隨之釋放。

        // ARC 
        ① (void)(__bridge_transfer id)p; //指定返回結果爲void ,不利用返回值 對象引用計數減1
        ② id obj = (__bridge_transfer id)p; // 若是返回值賦值給目標變量,轉換後對象引用計數不變
        // 等效 MRC 代碼
        ① [p release];
        ② id obj = (id)p; [obj retain];[(id)p release];

6.全部權修飾符

  1. __strong修飾符:對對象實例的強引用,是id類型或對象類型的默認全部權修飾符。持有強引用的變量在超出其做用域時被廢棄,隨着強引用的失效,引用的對象
    會隨之釋放。

    // 如下兩行代碼是等效的
    id obj = [[NSObject alloc] init];
    id __strong obj = [[NSObject alloc] init];
  2. __weak修飾符:提供弱引用,弱引用不能持有對象實例,與__strong修飾符相反。可用來解決循環引用形成的內存泄露問與題。
  3. __unsafe_unretained修飾符:不安全的全部權修飾符,儘管ARC式的內存管理屬於編譯器的工做,但附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象,既不持有對象的強引用也不持有對象的弱引用,對象的可能隨時被釋放,訪問釋放的對象運行時會崩潰。
  4. __autoreleasing修飾符:對象變量要爲自動變量(包含局部變量、函數以及方法參數)。利用「__objc_autoreleasePoolPrint()」調試autoreleasepool上的對象

    • 等價於MRC時調用對象的autorelease方法。如下兩段代碼等效:

      // MRC
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      id obj = [[NSObject alloc] init];
      [obj autorelease];
      [pool drain];
      // ARC
      @autoreleasepool {
          id __autoreleasing obj = [[NSObject alloc] init];
      }
    • 顯式的附加__autoreleasing修飾符和顯式度附加__strong修飾符同樣「罕見」,有些狀況下非
      顯式的聲明__autoreleasing修飾符也是能夠的,這些狀況會自動將對象註冊到自動釋放池。
      1.非本身生成並持有的對象

      @autoreleasepool {
          id  obj = [NSMutableArray array];
          // 變量obj爲對象的強引用(默認__strong),編譯器判斷方法名後(非alloc/new等)自動註冊大autoreleasepool
      }

      2.id的指針和對象的指針(二級指針)在沒有顯示的指定時會被附加上__autoreleasing修飾符。

      NSError *error = nil; //全部權爲__strong類型
       NSError **pError = &error; //編譯出錯, NSError * (對象)默認爲_autoreleasing類型,賦值先後全部權不匹配。
       NSError * __strong *pError = &error; //編譯正確

      3.本身生成並持有的對象做爲返回值

      + (id)array {
          id obj = [[NSMutableArray alloc] init];
          return obj;
        }

      4.雖然__weak修飾符是爲了不循環引用而使用的,但訪問附有__weak修飾符的變量時實j 際上一定要訪問註冊到autoreleasepool的對象爲了不該對象被廢棄,註冊到autoreleasepool以後能夠保證在autoreleasepool塊結束以前對象存在。

      id __weak obj1 = obj0;
       NSLog(@"class = %@",[obj1 class]);
       // 如下代碼和以上代碼相同
       id __weak obj1 = obj0;
       id __autoreleasing tmp = obj1;
       NSLog(@"class = %@",[tmp class]);

7.引用計數查看

Apple 提供一些方法查看對象的引用計數,可是並不能徹底信任這些函數提供的引用計數值。對於已釋放的對象一級不正確的對象地址,有時 也返回」1「,在多線程中,由於存在競態條件的問題,因此取得的的數值不必定可信。

[object retainCount]; //獲得object的引用計數,此方法僅僅適用於MRC
  _objc_rootRetainCount(object); // ARC和MRC適用,頭文件聲明:OBJC_EXTERN int _objc_rootRetainCount(id);
相關文章
相關標籤/搜索