「理解」iOS內存管理

引言:
咱們都知道Objective-C經過「引用計數」來管理對象釋放。基本原理就是管理對象的持有者個數(引用計數),引用計數爲0時釋放對象。如今有ARC(自動引用計數),則無需咱們本身顯式持有(retain)和釋放(release)對象,ARC經過對對像加上全部權修飾符(__strong等),編譯器經過對象的全部權修飾符將會自動鍵入引用計數管理(根據全部權修飾符自動鍵入retain、release、autorelease)javascript

本文主要敘述引用計數的實現原理,ARC和MRC在使用上的區別,以及編譯器在ARC中爲咱們作了什麼。java

Objective-C對象的MRC

  • retain
  • release
  • autorelease

autorelease的實現

使用棧(後進先出)來管理NSAutoreleasePool對象,所以能夠隨時拿到最近(hotPage)的NSAutoreleasePool,調用對象的autorelease方法,會在hotPage中的內部數組將對象加入進去。當NSAutoreleasePool出棧時,調用內部數組中元素的release方法便可。objective-c

retain/release的實現

引用計數表:使用散列表實現引用計數表,key爲對象的地址的散列值,value爲引用計數和內存塊地址。
retain:經過對象的地址在引用計數表中找到引用計數,若是retainCount超過最大值,則拋異常,不然retainCount加1。
release:經過對象的地址在引用計數表中找到引用計數,若是retainCount值爲0,則拋異常。不然retainCount減1,若是retainCount減1後爲0,則從引用計數表衝移除,並調用對象的dealloc方法。express

Objective-C對象的ARC

Objective-C對象的ARC是經過全部權修飾符來管理對象的持有和釋放。全部權修飾符一共有4種:數組

  • __strong 修飾符,默認的修飾符
  • __weak 修飾符
  • __unsafe_unretained 修飾符
  • __autoreleasing 修飾符

__strong修飾符的實現

取得本身生成而且持有對象:
使用ARC:安全

{
id obj1 = [NSObject new];
//至關於
//id __strong obj1 = [NSObject new];
}複製代碼

不使用ARC:框架

{
id obj1 = [NSObject new];
//在變量做用域結束時插入release
[obj1 release];
}複製代碼

取得非本身生成並持有對象:
使用ARC:函數

- (void)create {
    test = [self object];
    NSLog(@"ARC------------------ARC");
    NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}

- (id)object {
    id obj = [[MyObject alloc] init];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"ARC------------------ARC");
    NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}複製代碼

不使用ARC:優化

- (void)create {
    if (test) {
        NSLog(@"MRC------------------MRC");
        NSLog(@"release");
        [test release];
    }
    test = [self object];
    [test retain];
    NSLog(@"MRC------------------MRC");
    NSLog(@"after create count = %ld", [test retainCount]);
}

- (id)object {
    id obj = [[MyObject alloc] init];
    [obj autorelease];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"MRC------------------MRC");
    NSLog(@"retain count = %ld", [test retainCount]);
}複製代碼

打印結果:spa

**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:51.212 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.213 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] release**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:36:03.540 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:36:03.543 ARCLearn[53676:16388070] retain count = 1**複製代碼

能夠看到使用ARC的對象持有,在返回對象時,並無把對象註冊到autoreleasepool中,下面爲ARC的autoreleasepool打印:

**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 13 releases pending.**
**objc[54013]: [0x7fa3c9811000]  ................  PAGE  (hot) (cold)**
**objc[54013]: [0x7fa3c9811038]  ################  POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040]    0x60800002e3c0  __NSCFString**
**objc[54013]: [0x7fa3c9811048]  ################  POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811058]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811060]    0x6000002712c0  __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068]    0x7fa3c8600bf0  UIWindow**
**objc[54013]: [0x7fa3c9811070]    0x60000008e6f0  __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078]    0x6000000567d0  __NSSetM**
**objc[54013]: [0x7fa3c9811080]    0x600000052de0  __NSSetM**
**objc[54013]: [0x7fa3c9811088]    0x600000053b30  __NSSetM**
**objc[54013]: [0x7fa3c9811090]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c9811098]    0x600000271640  __NSCFString**
**objc[54013]: ##############**複製代碼

下面爲MRC的autoreleasepool打印:

**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 14 releases pending.**
**objc[54013]: [0x7fa3c9811000]  ................  PAGE  (hot) (cold)**
**objc[54013]: [0x7fa3c9811038]  ################  POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040]    0x60800002e3c0  __NSCFString**
**objc[54013]: [0x7fa3c9811048]  ################  POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811058]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811060]    0x6000002712c0  __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068]    0x7fa3c8600bf0  UIWindow**
**objc[54013]: [0x7fa3c9811070]    0x60000008e6f0  __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078]    0x6000000567d0  __NSSetM**
**objc[54013]: [0x7fa3c9811080]    0x600000052de0  __NSSetM**
**objc[54013]: [0x7fa3c9811088]    0x600000053b30  __NSSetM**
**objc[54013]: [0x7fa3c9811090]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c9811098]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c98110a0]    0x608000017200  MyObject**
**objc[54013]: ##############**複製代碼

在autoreleasepool裏有MyObject對象。
爲何ARC沒有把對象放到autoreleasepool裏了?它是怎樣持有非本身生成的對象的?
代碼:

{
    id __strong obj = [NSMutableArray array];
}複製代碼

轉換爲編譯器模擬代碼:

{
    id obj = objc_msgSend(NSMutableArray, @selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);
}複製代碼

objc_retainAutoreleasedReturnValue函數,顧名思義:讓obj持有(retain)池中(autoreleased)返回的值(retuenValue);
代碼:

+ (id)array {
    return [[NSMutableArray alloc] init];
}複製代碼

轉換爲編譯器模擬代碼:

+ (id)array {
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    return objc_autoreleaseReturnValue(obj);
}複製代碼

objc_autoreleaseReturnValue函數,顧名思義:把obj註冊在池中(調用obj的autorelease方法),並返回。
所以按照上面的理解objc_autoreleaseReturnValue將返回對象註冊到池子中,objc_retainAutoreleasedReturnValue`持有池中的對象。

可是事實不是那麼簡單:
可是objc_autoreleaseReturnValue遠遠不是autorelease那麼簡單。objc_autoreleaseReturnValue函數會檢查使用該函數的方法或函數調用方的執行命令列表,若是方法或函數的調用方在調用了方法或函數後緊接着調用了objc_retainAutoreleasedReturnValue,那就不會將返回的對象註冊到autoreleasepool中,而是直接傳遞到方法或函數的調用方。
所以objc_retainAutoreleasedReturnValue也不是retain那麼簡單,即便放回對象不註冊到autoreleasepool中,也能正確的獲取對象。
經過objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue協做,能夠不將對象註冊到autoreleasepool中而直接傳遞,這一過程達到了最優化。

__weak修飾符的實現

如今咱們添加一個weakTest變量,使用weak全部權修飾符
在ARC中實現:

@interface ARC() {
    id test;
    id __weak weakTest;
}

@end

@implementation ARC

- (void)create {
    test = [self object];
    weakTest = test;
    _objc_autoreleasePoolPrint();
    NSLog(@"ARC------------------ARC");
    NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}

- (id)object {
    id obj = [[MyObject alloc] init];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"ARC------------------ARC");
    NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}

@end複製代碼

打印結果:

**2016-12-13 16:24:30.934 ARCLearn[55038:16489628] ARC------------------ARC**
**2016-12-13 16:24:30.935 ARCLearn[55038:16489628] after create count = 1**複製代碼

由此能夠看出__weak並無增長引用個數。
添加empty函數,使test爲nil:

- (void)empty {
    if (weakTest) {
        NSLog(@"weak is %p", weakTest);
    }
    test = nil;
    if (!weakTest) {
        NSLog(@"weak change to nil");
    }
}複製代碼

打印結果:

**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] weak is 0x608000011530**
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] myobject dealloc**
**2016-12-13 16:33:06.302 ARCLearn[55349:16509066] weak change to nil**複製代碼

由此能夠看出若附有__weak修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量
是如何實現將nil值賦值給引用爲0的__weak對象?

{
    id __weak obj1 = obj;
}複製代碼

轉換爲編譯器模擬代碼:

{
    id obj1;
    objc_initWeak(&obj1, obj);
    objc_destroyWeak(obj1);
}複製代碼

其中objc_initWeak(&obj1,obj)的實現:

obj1 = 0;
objc_storyWeak(&obj1, obj);複製代碼

objc_destroyWeak的實現:

objc_storyWeak(&obj1, 0);複製代碼

因此合在一塊兒是:

{
    id obj1;
    obj1 = 0;
    objc_storyWeak(&obj1, obj);
    objc_storyWeak(&obj1, 0);
}複製代碼

其中objc_storyWeak就是針對weak表(散列表)操做:

  • 註冊到表中:objc_storeWeak函數把第二個參數的地址(散列值)做爲鍵,將第一個參數的地址加入鏈表中(鍵值爲一個鏈表,因爲一個對象可同時賦值給多個附有__weak修飾符的變量),完成weak表的註冊。
  • 從表中移除:若是第二個參數爲0,則把第二個參數的地址的鍵從weak表中刪除,而且將鍵值置爲nil(將鏈表中的值都置爲nil)。
  • 從表中查詢:使用weak表,將廢棄對象的地址做爲鍵進行檢索,就能告訴的獲取對應的附有__weak修飾符的變量的地址。

廢棄引用爲0的對象的流程:

  • objc_release
  • 由於引用計數爲0因此執行dealloc
  • 從weak表中獲取廢棄對象的地址爲鍵的記錄
  • 將包含在記錄中全部附有__weak修飾符變量的地址,賦值爲nil
  • 從weak表中刪除該記錄
  • 從引用計數表中刪除廢棄對象的地址爲鍵的記錄

__autoreleasing修飾符的實現

將對象賦值給附有__autoreleasing修飾符的變量等同於ARC無效時調用對象的autorelease方法。
ARC有效:

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];

    id __strong obj1 = [[NSObject alloc] init];
    id __autoreleasing obj2 = obj1;

    id __strong obj3 = [NSMutableArray array];
    id __autoreleasing obj4 = obj3;
}複製代碼

ARC無效:

@autoreleasepool {
    id obj = [[NSObject alloc] init];
    [obj autorelease];

    id obj1 = [[NSObject alloc] init];
    id obj2 = obj1;
    [obj2 retain];
    [obj2 autorelease];

    id obj3 = [NSMutableArray array];
    [obj3 retain];
    id obj4 = obj3;
    [obj4 retain];
    [obj4 autorelease];
    [obj3 release];
}複製代碼

注意:顯式的附加autoreleasing修飾符同顯式地添加strong修飾符同樣罕見。咱們常常非顯式的使用__autoreleasing修飾符。

隱式使用__autoreleasing:

  • 編譯器檢查方法名是否以alloc/new/copy/mutableCopy開始,若是不是則自動將返回值的對象註冊到autoreleasepool(objc_retainAutoreleasedReturnValue和objc_autoreleaseReturnValue成對出現時就不會註冊到autoreleasepool)。
  • id的指針或對象的指針在沒有顯式指定時會被附加上__autoreleasing修飾符。

如:

NSError *error = nil;
BOOL result = [obj performOperationWithError: &error];複製代碼

performOperationWithError聲明爲:

- (BOOL)performOperationWithError:(NSError **)error;複製代碼

自動添加__autoreleasing的代碼:

- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
    /* 錯誤發生 */
    *error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
    return NO;
}複製代碼

非ARC的實現:

- (BOOL)performOperationWithError:(NSError **)error {
    /* 錯誤發生 */
    *error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
    [*error autorelease];
    return NO;
}複製代碼

由於聲明爲NSError __autoreleasing 類型的error做爲*error被賦值,因此可以返回註冊到autoreleasepool中的對象。

注意:賦值給對象指針時,全部權修飾符必須一致。
編譯器會自動加上全部權修飾符:

NSError * __strong error = nil;
NSError * __autoreleasing *pError = &error;複製代碼

全部權修飾符不一致會產生編譯錯誤:
Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer

__unsafe_unretained 修飾符

unsafe_unretained修飾符正如其名unsafe所示,是不安全的全部權修飾符。儘管ARC式的內存管理是編譯器的工做,但附有unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。
ARC實現:

id obj = [[NSObject alloc] init];
id __unsafe_unretained obj1 = obj;複製代碼

非ARC實現:

id obj = [[NSObject alloc] init];
id obj1 = obj;複製代碼

因此就是...編譯器啥都不作。這樣當對象的引用計數爲0,被廢棄後,obj1就是不安全的了(懸垂指針),由於不會被置爲nil。

注意:使用時確保對象確實存在。

何時使用__unsafe_unretained?

  • 在iOS4的應用程序中,必須使用unsafe_unretained修飾的變量,來替代weak修飾符。
  • 不支持__weak修飾符的類,例如NSMachPork類,由於這些類重寫了retain/release並實現該類獨自的引用計數機制。
  • allowsWeakReference/retainWeakReference實例方法(沒有寫入NSObject接口說明文檔中)返回NO的狀況。

Core Foundation對象的ARC

Core Foundation對象主要使用在用C語言編寫的Core Foundation框架中,並使用引用計數的對象。

  • 在ARC無效時,Core Foundation框架中的retain/release分別是CFRetain/CFRelease。
  • Foundation框架的API生成並持有的對象能夠用Core Foundation框架的API釋放。固然,反過來也是能夠的。
  • 由於Core Foundation對象與Objective-C對象沒有區別,因此ARC無效時,只用簡單的C語言的轉換也能實現互換。由於這種轉換不須要使用額外的CPU資源,所以也被稱爲「免費橋」(Toll-free Bridge)。

ARC無效時:

id obj = [[NSObject alloc] init];
void *p = obj; //void *至關與oc的id

id o = p;
[o release];複製代碼

ARC有效時,會引發編譯錯誤,要使用「__bridge」轉換。
ARC有效時的代碼:

id obj = [[NSObject alloc] init];
void *p  = (__bridge void*)obj;

id o = (__bridge id)p;
[o release];複製代碼

注意:bridge的安全性與賦值給unsafe_unretained修飾符相近,甚至會更低,若是管理時不注意賦值對象的全部者,就會因懸垂指針而致使程序崩潰。

__bridge轉換中還有另兩中轉換,分別爲:

  • bridge_retained轉換,至關於賦值給strong變量。
    ARC有效:
    id obj = [[NSObject alloc] init];
    void *p = (__bridge_retained void*)obj;複製代碼
    ARC無效:
    id obj = [[NSObject alloc] init];
    void *p = obj;
    [(id)p retain];複製代碼
  • bridge_transfer轉換,至關於賦值給strong變量後,該變量隨之釋放。
    ARC有效
    void *p = 0;
    id obj = (__bridge_transfer id)p;複製代碼
    ARC無效
    void *p = 0;
    id obj = (id)p;
    [obj retain];
    [(id)p release];複製代碼

簡書我的主頁:www.jianshu.com/users/b92ab…

相關文章
相關標籤/搜索