ARC 的實現

本文爲 《Objective-C 高級編程》1.4 中的內容編程

ARC 由如下工具、庫來實現:bash

  • clang(LLVM 編譯器)3.0 以上
  • objc4 Objective-C 運行時庫 493.9 以上

經過 clang 彙編輸出和 objc4 庫(主要是 runtime/objc-arr.mm)的源代碼進行說明。架構

__strong 修飾符

賦值給附有 __strong 修飾符的變量在實際的程序中究竟是怎樣運行的呢?app

{
    id __strong obj = [[NSObject alloc] init];
}
複製代碼

在編譯器選項 「-S」 的同時運行 clang,可取得程序彙編輸出。看看彙編輸出和 objc4 庫的源代碼就可以知道程序是如何工做的。該源代碼實際上可轉換爲調用如下的函數。爲了便於理解,之後的源代碼有時也使用模擬源代碼。框架

/* 編譯器的模擬代碼*/
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
複製代碼

如原源代碼所示,2次調用 objc_msgSend 方法(alloc 方法和 init 方法),變量做用域結束時經過 objc_release 釋放對象。雖然 ARC 有效時不能使用 release 方法,但由此可知編譯器自動插入了 release。下面咱們來看看使用 alloc/new/copy/mutableCopy 之外的方法會是什麼狀況。ide

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

雖然調用了咱們熟知的 NSMutableArray 類的 array 類方法,但獲得的結果卻與以前稍有不一樣。函數

/* 編譯器的模擬代碼*/
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
複製代碼

雖然最開始的 array 方法的調用以及最後變量做用域結束時的 release 與以前相同,但中間的 objc_retainAutoreleasedReturnValue 函數是什麼呢?工具

objc_retainAutoreleasedReturnValue 函數主要用於最優化程序運行。顧名思義,它是用於本身持有(retain)對象的函數,但它持有的對象應爲返回註冊在 autoreleasepool 中對象的方法,或是函數的返回值。像該源代碼這樣,在調用 alloc/new/copy/mutableCopy 之外的方法,即 NSMutableArray 類的 array 類方法等調用以後,由編譯器插入該函數。優化

這種 objc_retainAutoreleasedReturnValue 函數是成對的,與之相對的函數是 objc_autoreleaseReturnValue。它用於 alloc/new/copy/mutableCopy 方法之外的 NSMutableArray 類的 array 類方法等返回對象的實現上。下面咱們看看 NSMutableArray 類的 array 類經過編譯器會進行怎樣的轉換。ui

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

如下爲該源代碼的轉換,轉換後的源代碼使用了 objc_autoreleaseReturnValue 函數。

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

像該源代碼這樣,返回註冊到 autoreleasepool 中對象的方法使用了 objc_autoreleaseReturnValue 函數返回註冊到 autoreleasepool 中的對象。可是 objc_autoreleaseReturnValue 函數同 objc_autorelease 函數不一樣,通常不只限於註冊對象到 autoreleasepool 中。

objc_autoreleaseReturnValue 函數會檢查使用該函數的方法或函數調用方的執行列表,若是方法或函數的調用方在調用了方法或函數後緊接着調用 objc_retainAutoreleaseReturnValue() 函數,那麼就不將返回的對象註冊到 autorelease 中,而是直接傳遞到方法或函數的調用方。objc_retainAutoreleasedReturnValue 函數與 objc_retain 函數不一樣,它即使不註冊到 autoreleasepool 中而返回對象,也可以正確地獲取對象。經過 objc_autoreleaseReturnValue 函數和 objc_retainAutoreleasedReturnValue 函數的寫做,能夠不將對象註冊到 autorelease 中而直接傳遞,這一過程達到了最優化。

objc_autoreleaseReturnValue

objc_autoreleaseReturnValue 實現以下:

// Prepare a value at +1 for return through a +0 autoreleasing convention.
id objc_autoreleaseReturnValue(id obj) {
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}
複製代碼

prepareOptimizedReturn 是優化返回的關鍵,其實現以下:

// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0);

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}
複製代碼

__builtin_return_address

gcc 的編譯特性使用 __builtin_return_address(level) 打印出一個函數的堆棧地址。其中 level 表明是堆棧中第幾層調用地址,__builtin_return_address(0) 表示第一層調用地址,即當前函數,__builtin_return_address(1) 表示第二層。

callerAcceptsOptimizedReturn

callerAcceptsOptimizedReturn 在 arm 架構上的實現以下:

static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) {
    // if the low bit is set, we're returning to thumb mode
    if ((uintptr_t)ra & 1) {
        // 3f 46 mov r7, r7
        // we mask off the low bit via subtraction
        // 16-bit instructions are well-aligned
        if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
            return true;
        }
    } else {
        // 07 70 a0 e1 mov r7, r7
        // 32-bit instructions may be only 16-bit aligned
        if (*(unaligned_uint32_t *)ra == 0xe1a07007) {
            return true;
        }
    }
    return false;
}
複製代碼

關於這段代碼的含義請看這篇文章,總結的比我好:How does objc_retainAutoreleasedReturnValue work?

objc_retainAutoreleasedReturnValue

objc_retainAutoreleasedReturnValue 實現以下,基本就是 objc_autoreleaseReturnValue 的逆操做。

// Accept a value returned through a +0 autoreleasing convention for use at +1.
id objc_retainAutoreleasedReturnValue(id obj) {
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}
複製代碼

__weak 修飾符

就像前面咱們看到的同樣,__weak 修飾符提供的功能如同魔法通常。

  • 若附有 __weak 修飾符的變量所引用的對象被廢棄,則將 nil 賦值給該變量。
  • 使用附有 __weak 修飾符的變量,便是使用註冊到 autoreleasepool 中的對象。

這些功能像魔法同樣,到底發生了什麼,咱們一無所知。因此下面咱們來看看它們的實現。

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

假設變量 obj 附加 __strong 修飾符且對象被賦值。

/* 編譯器的模擬代碼 */
id obj1;
objc_initWeak(&obj1, obj);
objc_destoryWeak(&obj1);
複製代碼

經過 objc_initWeak 函數初始化附有 __weak 修飾符的變量,在變量做用域結束時經過 objc_destroyWeak 函數釋放該變量。

如如下源代碼所示,objc_initWeak 函數將附有 __weak 修飾符的變量初始化爲0後,會將賦值的對象做爲參數調用 objc_storeWeak 函數。

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

objc_destroyWeak 函數將0做爲參數調用 objc_storeWeak 函數。

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

即前面的源代碼與下列源代碼相同。

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

objc_storeWeak 函數把第二參數的賦值對象的地址做爲鍵值,將第一參數的附有 __weak 修飾符的變量的地址註冊到 weak 表中。若是第二參數爲 0,則把變量的地址從 weak 表中刪除。

weak 表與引用計數表相同,做爲散列表被實現。若是使用 weak 表,將廢棄對象的地址做爲鍵值進行檢索,就能高速地獲取對應的附有 __weak 修飾符的變量的地址。另外,因爲一個對象可同時賦值給多個附有 __weak 修飾符的變量中,因此對於一個鍵值,可註冊多個變量的地址。

釋放對象時,廢棄誰都不持有的對象的同時,程序的動做是怎樣的呢?下面咱們來跟蹤觀察。對象將經過 objc_release 函數釋放。

  1. objc_release
  2. 由於引用計數爲 0 因此執行 dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

對象被廢棄時最後調用的 objc_clear_deallocating 函數的動做以下:

  1. 將 weak 表中獲取廢棄對象的地址爲鍵值的記錄。
  2. 將包含在記錄中的全部附有 __weak 修飾符變量的地址,賦值爲 nil。
  3. 從 weak 表中刪除改記錄。
  4. 從引用計數表中刪除廢棄對象的地址爲鍵值的記錄。

根據以上步驟,前面說的若是附有 __weak 修飾符的變量所引用的對象被廢棄,則將 nil 賦值給該變量這一功能即被實現。由此能夠,若是大量使用附有 __weak 修飾符的變量,則會消耗相應的 CPU 資源,良策是隻在須要避免循環引用時使用 __weak 修飾符。

使用 __weak 修飾符時,如下源代碼會引發編譯器警告。

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

由於該源代碼將本身生成並持有的對象賦值給附有 __weak 修飾符的變量中,因此本身不能持有該對象,這時會被釋放並廢棄,所以會引發編譯器警告。

編譯器會如何處理改源代碼呢?

id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init))l
objc_initWeak(tmp);
objc_release(tmp);
objc_destroyWeak(&object);
複製代碼

雖然本身生成並持有對象經過 objc_initWeak 函數被賦值給附有 __weak 修飾符的變量中,但編譯器判斷其沒有持有者,故該對象當即經過 objc_release 函數被釋放和廢棄。

這樣一來,nil 就會被賦值給引用廢棄對象的附有 __weak 修飾符的變量中。下面咱們經過 NSLog 函數來驗證一下。

id __weak obj = [[NSObject alloc] init];
NSLog(@"obj=%@", obj);
複製代碼

如下爲該源代碼的輸出結果,其中用 %@ 輸出 nil。

obj=(null)
複製代碼

此次咱們再用附有 __weak 修飾符的變量來確認另外一功能:使用附有 __weak 修飾符的變量,便是使用註冊到 autoreleasepool 中的對象。

{
    id __weak obj1 = obj;
    NSLog(@"%@", obj1);
}
複製代碼

該源代碼可轉換爲以下形式:

/* 編譯器的模擬代碼 */
id objl
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
複製代碼

與被賦值時相比,在使用附有 __weak 修飾符變量的情形下,增長了對 objc_loadWeakRetained 函數和 objc_autorelease 函數的調用。這些函數的動做以下:

  1. objc_loadWeakRetained 函數取出附有 __weak 修飾符變量所引用的對象並 retain。
  2. objc_autorelease 函數將對象註冊到 autoreleasepool 中。

由此可知,由於附有 __weak 修飾符變量縮陰用的對象像這樣被註冊到 autoreleasepool 中,因此在 @autoreleasepool 塊結束以前均可以放心使用。可是,若是大量地使用附有 __weak 修飾符的變量,註冊到 autorelease 的對象也會大量地增長,所以在使用附有 __weak 修飾符的變量時,最好先暫時賦值給附有 __weak 修飾符的變量後再使用。

{
    id __weak o = obj;
    NSLog(@"1 %@", o);
    NSLog(@"2 %@", o);
    NSLog(@"3 %@", o);
    NSLog(@"4 %@", o);
    NSLog(@"5 %@", o);
}
複製代碼

相應地,變量 o 所賦值的對象也就註冊到 autoreleasepool 中 5 次。

將附有 __weak 修飾符的變量 o 賦值給附有 __strong 修飾符的變量後再使用能夠避免此類問題。

{
    id __weak o = obj;
    id tmp = o;
    NSLog(@"1 %@", tmp);
    NSLog(@"2 %@", tmp);
    NSLog(@"3 %@", tmp);
    NSLog(@"4 %@", tmp);
    NSLog(@"5 %@", tmp);
}
複製代碼

在 "tmp = 0;" 時對象僅登陸到 autoreleasepool 中1次。

在 iOS 4 和 OS X Snow Leopard 中是不能使用 __weak 修飾符的,而有時在其餘環境下也不能使用。實際上存在着不支持 __weak 修飾符的類。

例如 NSMachPort 類就是不支持 __weak 修飾符的類。這些類重寫了 retain/release 並實現該類肚子的引用計數機制。可是賦值以及使用附有 __weak 修飾符的變量都必須恰當地使用 objc4 運行時庫中的函數,所以獨自實現引用計數機制的類大多不支持 __weak 修飾符。

不支持 __weak 修飾符的類,在其類聲明中附加了 "__attribute ((objc_arc_weak_reference_unavailable))" 這一屬性,同時定義了 NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE。若是將不支持 __weak 聲明類的對象賦值給附有 __weak 修飾符的變量,那麼一旦編譯器檢驗出來就會報告編譯錯誤。並且在 Cocoa 框架類中,不支持 __weak 修飾符的類極爲罕見,所以沒有必要太過擔憂。

objc_initWeak

objc_initWeak 方法的實現以下:

/** * Initialize a fresh weak pointer to some object location. * It would be used for code like: * * (The nil case) * __weak id weakPtr; * (The non-nil case) * NSObject *o = ...; * __weak id weakPtr = o; * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location Address of __weak ptr. * @param newObj Object ptr. */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
複製代碼

objc_destroyWeak

objc_destroyWeak 實現以下:

/** * Destroys the relationship between a weak pointer * and the object it is referencing in the internal weak * table. If the weak pointer is not referencing anything, * there is no need to edit the weak table. * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location The weak pointer address. */
void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
複製代碼

objc_storeWeak

/** * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return \e newObj */
id
objc_storeWeak(id *location, id newObj)
{
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}
複製代碼

storeWeak 方法實現以下

看了上述代碼咱們能夠知道,objc_initWeak、objc_destroyWeak 和 objc_storeWeak 三個方法本質上都調用了 storeWeak 方法,其實現以下

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
複製代碼

__unsafe_unretained

如前所述,如下源代碼會引發編譯警告。

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

這是因爲編譯器判斷生成並持有的對象不能繼續持有。附有 __unsafe_unretained 修飾符的變量又如何呢?

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

與 __weak 修飾符徹底相同,編譯器判斷生成並持有的對象不能繼續持有,從而發出警告。

該源代碼經過編譯器轉換爲如下形式。

/* 編譯器的模擬代碼 */
id obj = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
複製代碼

objc_release 函數當即釋放了生成並持有的對象,這樣該對象的懸垂指針被賦值給變量 obj 中。

那麼若是最初不賦值變量又會如何呢?下面的源代碼在 ARC 無效時一定會發生內存泄漏。

[[NSObject alloc] init];
複製代碼

因爲源代碼不使用返回值的對象,因此編譯器發出警告。

可像下面這樣經過向 void 型轉換來避免發生警告。

(void)[[NSObject alloc] init];
複製代碼

不論是否轉換爲 void,該源代碼都會轉換爲如下形式。

/* 編譯器的模擬代碼 */
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
複製代碼

雖然沒有指定賦值變量,但與賦值給附有 __unsafe_unretained 修飾符變量的源代碼徹底相同。因爲不能繼續持有生成並持有的對象,因此編譯器生成了當即調用 objc_release 函數的源代碼。而因爲 ARC 的處理,這樣的源代碼也不會形成內存泄露。

另外,能調用被當即釋放的對象的示例方法嗎?

(void)[[[NSObject alloc] init] hash];
複製代碼

該源代碼可變爲以下形式:

/* 編譯器的模擬代碼 */
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
複製代碼

在調用了生成並持有對象的實例方法後,該對象被釋放。看來「由編譯器進行內存管理」這句話應該是正確的。

allowsWeakReference/retainWeakReference 方法

實際上還有一種狀況也不能使用 __weak 修飾符。

就是當 allowsWeakReference/retainWeakReference 實例方法(沒有寫入 NSObject 接口說明文檔中)返回 NO 的狀況。這些方法的聲明以下:

- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
複製代碼

在賦值給 __weak 修飾符的變量時,若是賦值對象的 allowsWeakReference 方法返回 NO,程序將異常終止。

即對於全部 allowsWeakReference 方法返回 NO 的類都絕對不能使用 __weak 修飾符。這樣的類一定在其參考說明中有所記述。

另外,在使用 __weak 修飾符的變量時,當被賦值對象的 retainWeakReference 方法返回 NO 的狀況下,該變量將使用「nil」。如如下的源代碼:

{
    id __strong obj = [[NSObject alloc] init];
    id __weak o = obj;
    NSLog(@「1 %@」, o);
    NSLog(@「2 %@」, o);
    NSLog(@「3 %@」, o);
    NSLog(@「4 %@」, o);
    NSLog(@「5 %@」, o);
}
複製代碼

因爲最開始生成並持有的對象爲附有 __strong 修飾符變量 obj 所持有的強引用,因此在該變量做用域結束以前都始終存在。所以以下所示,在變量做用域結束以前,沒能夠持續使用附有 __weak 修飾符的變量 o 所引用的對象。

下面對 retainWeakReference 方法進行實驗。咱們作一個 MyObject 類,讓其繼承 NSObject 類並實現 retainWeakReference 方法。

@interface MyObject : NSObject
{
    NSUInteger count;
}
@end

@implementation MyObject

- (id)init
{
    self = [super init];
    return self;
}

- (BOOL)retainWeakReference
{
    if (++count > 3)
        return NO;
    return [super retainWeakReference];
}
@end
複製代碼

該例中,當 retainWeakReference 方法被調用4次或4次以上時返回 NO。在以前的源代碼中,將從 NSObject 類生成並持有對象的部分更改成 MyObject 類。

{
    id __strong obj = [[MyObject alloc] init];
    id __weak o = obj;
    NSLog(@"1 %@", o);
    NSLog(@"2 %@", o);
    NSLog(@"3 %@", o);
    NSLog(@"4 %@", o);
    NSLog(@"5 %@", o);
}
複製代碼

如下爲執行結果。

從第4次起,使用附有 __weak 修飾符的變量 o 時,因爲所飲用對象的 retainWeakReference 方法返回 NO,因此沒法獲取對象。像這樣的類也一定在其參考說明中有所記述。

另外,運行時庫爲了操做 __weak 修飾符在執行過程當中調用 allowsWeakReference/retainWeakReference 方法,所以從該方法中再次操做運行時庫時,其操做內容會永久等待。本來這些方法並無計入文檔,所以應用程序編程人員不可能實現該方法羣,但若是因某些緣由而不得不實現,那麼仍是在所有理解的基礎上實現比較好。

__autoreleasing 修飾符

將對象賦值給附有 __autoreleasing 修飾符的變量等同於 ARC 無效時調用對象的 autorelease 方法。咱們經過如下源代碼來看一下。

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}
複製代碼

該源代碼主要將 NSObject 類對象註冊到 autoreleasepool 中,可做以下變換:

/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
複製代碼

這與蘋果的 autorelease 實現中的說明徹底相同。雖然 ARC 有效和無效時,其在源代碼上的表現有所不一樣,但 autorelease 的功能徹底同樣。

在 alloc/new/copy/mutableCopy 方法羣以外的方法中使用註冊到 autoreleasepool 中的對象會如何呢?下面咱們來看看 NSMutableArray 類的 array 類方法。

@autoreleasepool {
    id __autoreleasing obj = [NSMutableArray array];
}
複製代碼

這與前面的源代碼有何不一樣呢?

/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
複製代碼

雖然持有對象的方法從 alloc 方法變爲 objc_retainAutoreleasedReturnValue 函數,但註冊 autoreleasepool 的方法沒有改變,還是 objc_autorelease 函數。

相關文章
相關標籤/搜索