理解 ARC 實現原理

ARC 是 iOS 中管理引用計數的技術,幫助 iOS 實現垃圾自動回收,具體實現的原理是由編譯器進行管理的,同時運行時庫協助編譯器輔助完成。主要涉及到 Clang (LLVM 編譯器) 和 objc4 運行時庫。c++

本文主要內容由修飾符 __strong 、 __weak 、 __autorelease 拓展開,分別延伸出引用計數、弱引用表、自動釋放池等實現原理。在閱讀本文以前,你能夠看看下面幾個問題:git

  • 在 ARC 下如何存儲引用計數?github

  • [NSDictionary dictionary]方法建立的對象在 ARC 中有什麼不一樣之處。算法

  • 弱引用表的數據結構。編程

  • 解釋一下自動釋放池中的 Hot Page 和 Cold Page。數組

若是上述幾個問題你已經很是清楚,那本文可能對你的幫助有限,但若是你對這幾個問題還存有疑問,那相信本文必定能解答你的疑問。緩存

1、Clang

在 Objective-C 中,對象的引用關係由引用修飾符來決定,如__strong__weak__autorelease等等,編譯器會根據不一樣的修飾符生成不一樣邏輯的代碼來管理內存。bash

首先看看 Clang 在其中具體起到哪些做用,咱們能夠在命令行使用下面的命令來將 Objective-C 代碼轉成 LLVM 中間碼:數據結構

// 切換到你文件路徑下
cd Path
// 利用 main.m 生成中間碼文件 main.ll
clang -S -fobjc-arc -emit-llvm main.m -o main.ll 
複製代碼

我在main.m文件中加入defaultFunction方法,而後利用的命令行命令將其轉換成中間碼:app

void defaultFunction() {
    id obj = [NSObject new];
}
複製代碼

在命令行輸入命令後你能夠在文件夾下面發現main.ll,它的內容以下:

define void @defaultFunction() #0 {
  %1 = alloca i8*, align 8
  %2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
  %4 = bitcast %struct._class_t* %2 to i8*
  %5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3)
  %6 = bitcast i8* %5 to %0*
  %7 = bitcast %0* %6 to i8*
  store i8* %7, i8** %1, align 8
  call void @objc_storeStrong(i8** %1, i8* null) #4
  ret void
}
複製代碼

雖然內容有點多,可是仔細分析下來大概就是如下內容:

void defaultFunction() {
	id obj = obj_msgSend(NSObject, @selector(new));
	objc_storeStrong(obj, null);
}
複製代碼

obj_msgSend(NSObject, @selector(new))很是好理解,就是新建一個對象,而objc_storeStrong是 objc4 庫中的方法,具體邏輯以下:

void objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}
複製代碼

上面的代碼按順序作了如下 4 件事:

  1. 檢查輸入的 obj 地址 和指針指向的地址是否相同。
  2. 持有對象,引用計數 + 1 。
  3. 指針指向 obj。
  4. 原來指向的對象引用計數 - 1。

其中objc_retainobjc_release也是 objc4 庫中的方法,在本文後面分析 objc4 庫的章節會詳細講。

2、 isa

在分析 ARC 相關源碼以前,須要對 isa 有必定了解,其中存儲了一些很是重要的信息,下面是 isa 的結構組成:

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用優化的isa指針
         uintptr_t has_assoc         : 1;//->是否包含關聯對象
         uintptr_t has_cxx_dtor      : 1;//->是否設置了析構函數,若是沒有,釋放對象更快
         uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->類的指針
         uintptr_t magic             : 6;//->固定值,用於判斷是否完成初始化
         uintptr_t weakly_referenced : 1;//->對象是否被弱引用
         uintptr_t deallocating      : 1;//->對象是否正在銷燬
         uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存儲引用計數將要溢出的時候,藉助Sidetable(散列表)存儲引用計數,has_sidetable_rc設置成1
        uintptr_t extra_rc          : 19;  //->存儲引用計數
    };
};
複製代碼

其中nonpointerweakly_referencedhas_sidetable_rcextra_rc都是 ARC 有直接關係的成員變量,其餘的大多也有涉及到。

struct objc_object {
    isa_t isa;
};
複製代碼

從下面代碼能夠知道,objc_object就是 isa 基礎上一層封裝。

struct objc_class : objc_object {
    isa_t isa; // 繼承自 objc_object
    Class superclass;
    cache_t cache; // 方法實現緩存和 vtable
    class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
複製代碼

objc_class繼承了objc_object,結構以下:

  • isa:objc_object 指向類,objc_class 指向元類。
  • superclass:指向父類。
  • cache:存儲用戶消息轉發優化的方法緩存和 vtable 。
  • bits:class_rw_t 和 class_ro_t ,保存了方法、協議、屬性等列表和一些標誌位。

3、 __strong 修飾符

在 MRC 時代 Retain 修飾符將會使被引用的對象引用計數 + 1 ,在 ARC 中 __strong 修飾符做爲其替代者,具體起到什麼樣的做用?咱們能夠經過 Clang 將 Objective-C 代碼轉成 LLVM 來分析其中原理。

3.1 __strong 修飾符的中間碼

接下來繼續將 Objective-C 代碼轉成 LLVM 中間碼,此次咱們試一下__strong修飾符:

void strongFunction() {
    id obj = [NSObject new];
    __strong id obj1 = obj;
}
複製代碼

中間碼(後面的中間碼爲了方便理解就刪除無用代碼):

void defaultFunction() {
	id obj = obj_msgSend(NSObject, @selector(new));
	id obj1 = objc_retain(obj)
	objc_storeStrong(obj, null);
	objc_storeStrong(obj1, null);
}
複製代碼

上面代碼一看就是很是常規的操做,建立對象、引用計數 + 1 、 分別釋放,將objc_storeStrong裏面的邏輯嵌入可得:

void defaultFunction() {
	id obj = obj_msgSend(NSObject, @selector(new));
	id obj1 = objc_retain(obj)
	objc_release(obj);
	objc_release(obj1);
}
複製代碼

3.2 objc_retain

接下來咱們經過分析 objc4 庫的源碼來了解objc_retainobjc_release的內部邏輯。先看objc_retain具體實現:

id objc_retain(id obj) {
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
複製代碼

繼續往下查看最終定位到objc_object::rootRetain方法:

ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
    // 若是是 TaggedPointer 直接返回
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 獲取 isa
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 未優化的 isa 部分
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            // 
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // 正在被釋放的處理
        // donot check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        // extra_rc 未溢出時引用計數++
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        // extra_rc 溢出
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                // 從新調用該函數 入參 handleOverflow 爲 true
                return rootRetain_overflow(tryRetain);
            }
            // 保留一半引用計數
            // 準備將另外一半複製到 side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
        //  更新 isa 值
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // 將另外一半複製到 side table side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
複製代碼

上面的代碼分紅 3 個小分支:

  • TaggedPointer:值存在指針內,直接返回。
  • !newisa.nonpointer:未優化的 isa ,使用sidetable_retain()
  • newisa.nonpointer:已優化的 isa , 這其中又分 extra_rc 溢出和未溢出的兩種狀況。
    • 未溢出時,isa.extra_rc + 1 完事。
    • 溢出時,將 isa.extra_rc 中一半值轉移至sidetable中,而後將isa.has_sidetable_rc設置爲true,表示使用了sidetable來計算引用次數。

3.3 objc_release

繼續看objc_release具體實現,最終定位到objc_object::rootRelease方法:

ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 未優化 isa
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            // 入參是否要執行 Dealloc 函數,若是爲 true 則執行 SEL_dealloc
            return sidetable_release(performDealloc);
        }

        // extra_rc --
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // donot ClearExclusive()
            goto underflow;
        }
        // 更新 isa 值
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
 	// 處理下溢,從 side table 中借位或者釋放

    newisa = oldisa;
    // 若是使用了 sidetable_rc
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
        	// 調用本函數處理下溢
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // 從 sidetable 中借位引用計數給 extra_rc
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        if (borrowed > 0) {
			// extra_rc 是計算額外的引用計數,0 即表示被引用一次
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
                                                
            // 保存失敗,恢復現場,重試                                    
            if (!stored) {
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

			// 若是仍是保存失敗,則還回 side table
            if (!stored) {
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }
    // 沒有使用 sidetable_rc ,或者 sidetable_rc 計數 == 0 的就直接釋放

    // 若是已是釋放中,拋個過分釋放錯誤
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    // 更新 isa 狀態
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

	// 執行 SEL_dealloc 事件
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}
複製代碼

這麼一長串代碼,將其分解後和 rootRetain 邏輯相似:

  • TaggedPointer: 直接返回 false。
  • !nonpointer: 未優化的 isa 執行 sidetable_release。
  • nonpointer:已優化的 isa ,分下溢和未下溢兩種狀況。
    • 未下溢: extra_rc--。
    • 下溢:從 sidetable 中借位給 extra_rc 達到半滿,若是沒法借位則說明引用計數歸零須要進行釋放。其中借位時可能保存失敗會不斷重試。

到這裏能夠知道 引用計數分別保存在isa.extra_rcsidetable中,當isa.extra_rc溢出時,將一半計數轉移至sidetable中,而當其下溢時,又會將計數轉回。當兩者都爲空時,會執行釋放流程

3.4 rootRetainCount

objc_object::rootRetainCount方法是用來計算引用計數的。經過前面rootRetainrootRelease的源碼分析能夠看出引用計數會分別存在isa.extra_rcsidetable。中,這一點在rootRetainCount方法中也獲得了體現。

inline uintptr_t objc_object::rootRetainCount()
{
	// TaggedPointer 直接返回
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    // 加載 isa
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    // 優化的 isa 須要 sidetable + bits.extra_rc + 1
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
	// 未優化返回 sidetable_retainCount
    sidetable_unlock();
    return sidetable_retainCount();
}
複製代碼

3.5 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue

在 MRC 時代有一句話叫 誰建立誰釋放 ,意思是由開發者經過allocnewcopymutableCopy等方法建立的對象,須要開發者手動釋放,而由其餘方法建立並返回的對象返回給用戶後也不須要開發者釋放,好比說由[NSMutableArray array]方法建立的數組,這樣的對象默認由自動釋放池管理。進入 ARC 時代後,針對返回的對象編譯器也作了一些特殊處理,具體經過下面的內容來理解其中奧妙。

首先將下方建立數組的代碼轉成中間碼:

id strongArrayInitFunction() {
    return [[NSMutableArray alloc] init];
}

void strongArrayFunction() {
    __strong id obj = [NSMutableArray array];
}
複製代碼

中間碼提煉後獲得下面的代碼:

strongArrayInitFunction() {
	id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
	objc_autoreleaseReturnValue(obj);
	return obj;
}

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

相對於用戶建立的對象,[NSMutableArray array]方法建立返回的對象轉換後多出了一個objc_retainAutoreleasedReturnValue方法。這涉及到一個最優化處理:

  1. 爲了節省了一個將對象註冊到autoreleasePool的操做,在執行objc_autoreleaseReturnValue時,根據查看後續調用的方法列表是否包含objc_retainAutoreleasedReturnValue方法,以此判斷是否走優化流程。
  2. 在執行objc_autoreleaseReturnValue時,優化流程將一個標誌位存儲在 TLS (Thread Local Storage) 中後直接返回對象。
  3. 執行後續方法objc_retainAutoreleasedReturnValue時檢查 TLS 的標誌位判斷是否處於優化流程,若是處於優化流程中則直接返回對象,而且將 TLS 的狀態還原。

下面再經過源代碼來進行分析,先看看objc_autoreleaseReturnValue具體實現:

id objc_autoreleaseReturnValue(id obj) {
	// 若是走優化程序則直接返回對象
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
	// 不然仍是走自動釋放池
    return objc_autorelease(obj);
}

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0);
	// 檢查使用該函數的方法或調用方的的調用列表,若是緊接着執行 objc_retainAutoreleasedReturnValue ,將不註冊到 autoreleasePool 中
    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
    	// 設置標記 ReturnAtPlus1
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

// 將 ReturnAtPlus1 或 ReturnAtPlus0 存入 TLS
static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) {
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

// 取出標記
static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
複製代碼

objc_autoreleaseReturnValue代碼邏輯大概分爲:

  1. 檢查調用者方法裏後面是否緊跟着調用了objc_retainAutoreleasedReturnValue
  2. 保存 ReturnAtPlus1 至 TLS 中。
  3. 使用了優化處理則返回對象,不然加入自動釋放池。

下面是objc_retainAutoreleasedReturnValue的源碼分析:

id objc_retainAutoreleasedReturnValue(id obj) {
	// 若是 TLS 中標記表示使用了優化程序,則直接返回
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {
	// 取出標記後返回
    ReturnDisposition disposition = getReturnDisposition();
    // 還原至未優化狀態
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}
複製代碼

objc_retainAutoreleasedReturnValue代碼邏輯大概分爲:

  1. 取出 TLS 中標記。
  2. 重置 TLS 中標記至 ReturnAtPlus0 。
  3. 判斷使用了優化處理則返回對象,不然引用計數 + 1。

經過分析源碼能夠得知下面這段代碼的優化流程和未優化流程是有挺大的區別的:

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

最終優化流程至關於:

id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_release(obj);
複製代碼

而未優化流程至關於:

id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autorelease(obj);
objc_retain(obj);
objc_release(obj);
複製代碼

4、__weak 修飾符

衆所周知,weak 表示弱引用,引用計數不會增長。在原對象釋放後,弱引用變量也會隨之被清除,接下來一步步分析其中原理。

4.1 __weak 修飾符的中間碼

首先將下面代碼轉換成中間碼:

void weakFunction() {
    __weak id obj = [NSObject new];
}

void weak1Function() {
    id obj = [NSObject new];
    __weak id obj1 = obj;
}

void weak2Function() {
    id obj = [NSObject new];
    __weak id obj1 = obj;
    NSLog(@"%@",obj1);
}
複製代碼

下面是轉化提煉後的中間碼:

weakFunction() {
	id temp = objc_msgSend(NSObject, @selector(new));
	objc_initWeak(&obj, temp);
	objc_release(temp);
	objc_destroyWeak(obj);
}

weak1Function() {
	id obj = objc_msgSend(NSObject, @selector(new));
	objc_initWeak(&obj1, obj);
	objc_destroyWeak(obj1);
	objc_storeStrong(obj, null);
}

weak2Function() {
	id obj = objc_msgSend(NSObject, @selector(new));
	objc_initWeak(obj1, obj);
	id temp = objc_loadWeakRetained(obj1);
	NSLog(@"%@",temp);
	objc_release(temp);
	objc_destroyWeak(obj1);
	objc_storeStrong(obj, null);
}	
複製代碼
  • weakFunction: 在該方法中聲明 __weak 對象後並無使用到,因此在objc_initWeak後,當即釋放調用了objc_releaseobjc_destroyWeak方法。

  • weak1Function:該方法中obj是強引用,obj1是弱引用,objc_initWeakobjc_destroyWeak前後成對調用,對應着弱引用變量的初始化和釋放方法。

  • weak2Function:和weak1Function不一樣之處是使用了弱引用變量obj1,在使用弱引用變量以前,編譯器建立了一個臨時的強引用對象,在用完後當即釋放。

4.2 objc_initWeak 和 objc_destroyWeak

4.2.1 objc_initWeak 和 objc_destroyWeak

下面是objc_initWeakobjc_destroyWeak的代碼實現:

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
	// 該地址沒有值,正賦予新值,若是正在釋放將會 crash
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}


void objc_destroyWeak(id *location) {
	// 該地址有值,沒有賦予新值,若是正在釋放不 crash
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

複製代碼

經過源代碼能夠發現最終都是經過storeWeak來實現各自邏輯的,在查看storeWeak實現以前,咱們要先了解一下它的模板參數的含義:

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

其中DontHaveOldDoHaveNewDoCrashIfDeallocating都是模板參數,具體含義以下:

enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // 是否有值
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // 是否有新值
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
}; // 操做正在釋放中的對象是否 Crash
複製代碼

4.2.2 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;

 retry:
    // 從 SideTables 中取出存儲弱引用表的 SideTable(爲弱引用表 weak_table_t 的一層封裝)
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    // location 指向的值發生改變,則從新執行獲取 oldObj
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // 若是有新值
    if (haveNew  &&  newObj) {
        // 若是該對象類還未初始化則進行初始化
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 建立一個非元類,而且初始化,會調用 +initialize 函數
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    
    // 若是有舊值,清除舊值對應的弱引用表
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 若是賦予了新值,註冊新值對應的弱引用表
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);

        // 設置 isa 標誌位 weakly_referenced 爲 true
        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;
}
複製代碼

這段代碼大概作了這幾件事:

  1. 從全局的哈希表SideTables中,利用對象自己地址進行位運算後獲得對應下標,取得該對象的弱引用表。SideTables是一個 64 個元素長度的散列表,發生碰撞時,可能一個SideTable中存在多個對象共享一個弱引用表。
  2. 若是有分配新值,則檢查新值對應的類是否初始化過,若是沒有,則就地初始化。
  3. 若是 location 有指向其餘舊值,則將舊值對應的弱引用表進行註銷。
  4. 若是分配了新值,將新值註冊到對應的弱引用表中。將isa.weakly_referenced設置爲true,表示該對象是有弱引用變量,釋放時要去清空弱引用表。

4.2.3 weak_register_no_lock 和 weak_unregister_no_lock

在上面的代碼中使用到weak_register_no_lockweak_unregister_no_lock來進行弱引用表的註冊和註銷,繼續查看這兩個方法的實現:

id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id; // 被引用的對象
    objc_object **referrer = (objc_object **)referrer_id; // 弱引用變量
    
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
    // 檢查當前對象沒有在釋放中
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) =
        (BOOL(*)(objc_object *, SEL))
        object_getMethodImplementation((id)referent,
                                       SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
        ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    
    // 若是正在釋放中,則根據 crashIfDeallocating 判斷是否觸發 crash
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }
    
    weak_entry_t *entry; // 每一個對象對應的一個弱引用記錄
    // 若是當前表中有該對象的記錄則直接加入該 weak 表中對應記錄
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    }
    else {
        // 沒有在 weak 表中找到對應記錄,則新建一個記錄
        weak_entry_t new_entry(referent, referrer);
        // 查看 weak_table 表是否要擴容
        weak_grow_maybe(weak_table);
        // 將記錄插入 weak 表中
        weak_entry_insert(weak_table, &new_entry);
    }
        
    return referent_id;
}
複製代碼

上面這段代碼主要邏輯:

  1. 檢查是否正在被釋放中,若是是則根據crashIfDeallocating判斷是否觸發 crash 。
  2. 檢查weak_table中是否有被引用對象對應的entry,若是有則直接將弱引用變量指針地址加入該entry中。
  3. 若是weak_table沒有找到對應的entry,則新建一個entry,並將弱引用變量指針地址加入entry中。同時檢查weak_table是否須要擴容。

下面是weak_unregister_no_lock代碼實現:

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id; // 被引用的對象
    objc_object **referrer = (objc_object **)referrer_id; // 弱引用變量

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 找到 weak 表中對應記錄後,將引用從記錄中移除
        remove_referrer(entry, referrer);
        
        // 移除後檢查該引用記錄是否爲空
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        // 若是當前記錄爲空則移除記錄
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}
複製代碼

上面這段代碼主要邏輯:

  1. weak_table中根據找到被引用對象對應的entry,而後將弱引用變量指針referrerentry中移除。
  2. 移除弱引用變量指針referrer以後,檢查entry是否爲空,若是爲空將其從weak_table中移除。

4.2.4 weak_table

上面的代碼中使用weak_table保存被引用對象的entry,下面繼續經過分析weak_table的增刪查函數的具體實現:

static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil;

	// hash_pointer 對地址作位運算得出哈希表下標的方式
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 線性探測,若是該下標存儲的是其餘對象,那往下移,直至找到正確的下標。
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask; // 不能超過 weak_table 最大長度限制
        
        // 回到初始下標,異常報錯
        if (index == begin) bad_weak_table(weak_table->weak_entries); 
        
        // 每次衝突下移 hash_displacement + 1,當前位移不超過記錄在案的最大位移
        hash_displacement++; 
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}
複製代碼

上述代碼就是weak_table查找entry的過程,也是哈希表尋址過程,使用線性探測的方法解決哈希衝突的問題:

  1. 經過被引用對象地址計算得到哈希表下標。
  2. 檢查對應下標存儲的是否是咱們要找到地址,若是是則返回該地址。
  3. 若是不是則繼續往下找,直至找到。在下移的過程當中,下標不能超過weak_table最大長度,同時hash_displacement不能超過記錄的max_hash_displacement最大哈希位移。max_hash_displacement是全部插入操做時記錄的最大哈希位移,若是超過了,那確定是出錯了。

下面是weak_table插入entry的代碼實現:

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);

	// 經過哈希算法獲得下標
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    
    // 判斷當前下標是否爲空,若是不是繼續往下尋址空位
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);  
        hash_displacement++;
    }

	// 找到空位後存入
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

	// 更新最大哈希位移值
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}
複製代碼

和查過過程相似,weak_table插入entry的的步驟:

  1. 經過被引用對象地址計算得到哈希表下標。
  2. 檢查對應下標是否爲空,若是不爲空繼續往下查找,直至找到空位。
  3. 將弱引用變量指針存入空位,同時更新weak_table的當前成員數量num_entries和最大哈希位移max_hash_displacement

下面是weak_table移除entry的代碼實現:

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // 釋放 entry 中的全部弱引用
    if (entry->out_of_line()) free(entry->referrers);
    // 置空指針
    bzero(entry, sizeof(*entry));
	// 更新 weak_table 對象數量,並檢查是否能夠縮減表容量
    weak_table->num_entries--;
    weak_compact_maybe(weak_table);
}
複製代碼

weak_table移除entry的的步驟:

  1. 釋放entry和其中的弱引用變量。
  2. 更新 weak_table 對象數量,並檢查是否能夠縮減表容量

4.2.5 entry 和 referrer

在弱引用表中entry對應着被引用的對象,而referrer表明弱引用變量。每次被弱引用時,都會將弱引用變量指針referrer加入entry中,而當原對象被釋放時,會將entry清空並移除。

下面看往entry中添加referrer的具體實現:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // inline_referrers 未超出時,直接加入 inline_referrers 中
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }
        // 若是 inline_referrers 超出 WEAK_INLINE_COUNT 數量,則執行下面代碼
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        
        // 將 inline_referrers 的引用轉移只 new_referrers
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // 修改 entry 內容及標誌位
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    assert(entry->out_of_line());
    // 當負載因子太高進行擴容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    // 根據地址計算下標
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 該下表位置下不爲空,發生 hash 碰撞了,
    while (entry->referrers[index] != nil) {
        // 後移
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    // 記錄最大位移
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    // 找到合適下標後存儲
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
複製代碼

entry的結構和weak_table類似,都使用了哈希表,而且使用線性探測法尋找對應位置。在此基礎上有一點不一樣的地方:

  1. entry有一個標誌位out_of_line,最初時該標誌位爲falseentry使用的是一個有序數組inline_referrers的存儲結構。
  2. inline_referrers的成員數量超過了WEAK_INLINE_COUNTout_of_line標誌位變成true,開始使用哈希表存儲結構。每當哈希表負載超過 3/4 時會進行擴容。

繼續看從entry移除referrer的具體實現:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        // 未超出 inline_referrers 時直接將對應位置清空
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }
    // 超出 inline_referrers 的邏輯
    
    // 根據地址計算下標
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 發生哈希衝突繼續日後查找
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    // 找到後將對應位置置空
    entry->referrers[index] = nil;
    entry->num_refs--;
}

複製代碼

entry移除referrer的步驟:

  1. out_of_linefalse時,從有序數組inline_referrers中查找並移除。
  2. out_of_linetrue時,從哈希表中查找並移除。

4.2.6 dealloc

當被引用的對象被釋放後,會去檢查isa.weakly_referenced標誌位,每一個被弱引用的對象weakly_referenced標誌位都爲true

- (void)dealloc {
    _objc_rootDealloc(self);
}
複製代碼

順着dealloc方法邏輯往下走直至clearDeallocating_slow:

NEVER_INLINE void objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
	// 根據指針獲取對應 weak_table
    SideTable& table = SideTables()[this];
    table.lock();
    // 判斷若是有被弱引用則清空該對象對應的 entry
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 清空該對象存儲在 sidetable 中的引用計數
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
複製代碼

從上面的代碼能夠看出,在對象釋執行dealloc函數時,會檢查isa.weakly_referenced標誌位,而後判斷是否要清理weak_table中的entry

4.3 objc_loadWeakRetained

經過前面的中間碼分析能夠得知,在使用弱引用變量以前,編譯器建立了一個臨時的強引用對象,以此保證使用時不會由於被釋放致使出錯,在用完後當即釋放。

下面看看如何對弱引用指針指向對象進行強引用:

id objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
	// 獲得弱引用指針指向對象
    obj = *location;
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj; // TaggedPointer 直接返回
    
    // 獲得對應 weak_table
    table = &SideTables()[obj];
    
    // 若是被引用對象在此期間發生變化則重試
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // 類和超類沒有自定義 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 等方法
        assert(cls->isInitialized());
        // 嘗試 retain
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            // 獲取自定義 SEL_retainWeakReference 方法
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, SEL_retainWeakReference);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            // 調用自定義函數
            else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        }
        else {
            // 類未初始化,則初始化後回到 retry 從新執行
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}
複製代碼

上面的代碼主要邏輯:

  1. 經過弱引用指向的對象,獲取弱引用表,而且將其上鎖,防止在此期間被清除。
  2. 判斷是否包含自定義retain方法,若是沒有,則使用默認rootTryRetain方法,使引用計數 + 1 。
  3. 若是使用了自定義retain方法,則調用自定義方法,在調用以前會先判斷該對象所屬類是否已經初始化過,若是沒有初始化會先進行初始化而後再調用。

5、__autorelease 修飾符

在 ARC 環境下, __autorelease 修飾符能夠將對象加入自動釋放池中,由自動釋放池管理釋放。

5.1 __autorelease 修飾符的中間碼

將下面的代碼轉換成中間碼查看編譯器的處理:

void autoReleasingFunction() {
    @autoreleasepool {
        __autoreleasing id obj = [NSObject new];
    }
}
複製代碼

轉換後的中間碼:

void autoReleasingFunction() {
	id token = objc_autoreleasePoolPush();
	id obj = objc_msgSend(NSObject, @selector(new));
	objc_autorelease(obj);
	objc_autoreleasePoolPop(token);	
}
複製代碼

經過上面的代碼能夠分析出:

  • @autoreleasepool{}關鍵字經過編譯器轉換成objc_autoreleasePoolPushobjc_autoreleasePoolPop這一對方法。
  • __autoreleasing 修飾符轉換成objc_autorelease,將obj加入自動釋放池中。

從上面的分析能夠看出,編譯器對自動釋放池的處理邏輯大體分紅:

  1. objc_autoreleasePoolPush做爲自動釋放池做用域的第一個函數。
  2. 使用objc_autorelease將對象加入自動釋放池。
  3. objc_autoreleasePoolPop做爲自動釋放池做用域的最後一個函數。

5.2 自動釋放池的預備知識

在繼續往下看以前,咱們須要先了解一些關於自動釋放池的相關知識:

自動釋放池都是由一個或者多個AutoreleasePoolPage組成,page的 SIZE 爲 4096 bytes ,它們經過parentchild指針組成一個雙向鏈表。

  • hotPage:是當前正在使用的page,操做都是在hotPage上完成,通常處於鏈表末端或者倒數第二個位置。存儲在 TLS 中,能夠理解爲一個每一個線程共享一個自動釋放池鏈表。

  • coldPage:位於鏈表頭部的page,可能同時爲hotPage

  • POOL_BOUNDARYnil的宏定義,替代以前的哨兵對象POOL_SENTINEL,在自動釋放池建立時,在objc_autoreleasePoolPush中將其推入自動釋放池中。在調用objc_autoreleasePoolPop時,會將池中對象按順序釋放,直至遇到最近一個POOL_BOUNDARY時中止。

  • EMPTY_POOL_PLACEHOLDER:當自動釋放池中沒有推入過任何對象時,這個時候推入一個POOL_BOUNDARY,會先將EMPTY_POOL_PLACEHOLDER存儲在 TLS 中做爲標識符,而且這次並不推入POOL_BOUNDARY。等再次有對象被推入自動釋放池時,檢查在 TLS 中取出該標識符,這個時候再推入POOL_BOUNDARY

  • next:指向AutoreleasePoolPage指向棧頂空位的指針,每次加入新的元素都會往上移動。

5.3 objc_autoreleasePoolPush

objc_autoreleasePoolPush方法的代碼實現:

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        // 測試狀態下,每一個自動釋放池都會新建一個 page
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
    	// 推一個 POOL_BOUNDARY 入棧,表示該釋放池的起點
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
複製代碼

objc_autoreleasePoolPush方法其實就是向自動釋放池推入一個POOL_BOUNDARY,做爲該autoreleasepool的起點。autoreleaseFast方法的具體邏輯將在後面分析autorelease方法時再進行分析。

5.4 autorelease

下面看加入自動釋放池方法autorelease的代碼實現:

static inline id autorelease(id obj) {
        id *dest __unused = autoreleaseFast(obj);
        return obj;
}
複製代碼

繼續往下看:

static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage();
    
    if (page && !page->full()) {
        // 有 hotPage 且不滿 直接加入棧中
        return page->add(obj);
    } else if (page) {
        // hotPage 已滿 先建立一個 Page 後,加入新 Page 中
        return autoreleaseFullPage(obj, page);
    } else {
        // 沒有 hotPage 直接新建一個 Page,並加入 Page 中
        return autoreleaseNoPage(obj);
    }
}
複製代碼

上面這段代碼邏輯:

  • 若是hotPage存在且未滿,則直接推入hotPage
  • 若是hotPage存在且已滿,調用autoreleaseFullPage
  • 若是hotPage不存在,調用autoreleaseNoPage

往下繼續分析autoreleaseFullPage的實現:

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);
    // 找到一個未滿的 page , 未找到則新建一個 page ,設置成 hotPage 
    do { 
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
    setHotPage(page);
    return page->add(obj);
}
複製代碼

該方法是在hotPage已滿的狀況下執行,具體邏輯以下:

  1. 查看hotPage是否有後繼節點,若是有直接使用後繼節點。
  2. 若是沒有後繼節點,則新建一個AutoreleasePoolPage
  3. 將對象加入獲取到的page,並將其設置爲hotPage,其實就是存入 TLS 中共享。

下面開始分析autoreleaseNoPage方法代碼實現:

id *autoreleaseNoPage(id obj)
{
    // 執行 No page 表示目前尚未釋放池,或者有一個空佔位符池,可是尚未加入對象
    assert(!hotPage());
    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        // 若是是空佔位符池,須要加入一個釋放池邊界
        pushExtraBoundary = true;
    }
    else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
        // We are pushing an object with no pool in place, 
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     pthread_self(), (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    // 若是傳入 POOL_BOUNDARY 則設置空池佔位符
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        return setEmptyPoolPlaceholder();
    }
    // We are pushing an object or a non-placeholder d pool.
    // 初始化一個 page 並設置 hotPage
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    // 插入釋放池邊界
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    // 對象加入釋放池
    return page->add(obj);
}
複製代碼

autoreleaseNoPage只有在自動釋放池尚未page時調用,主要邏輯:

  1. 若是當前自動釋放池推入的是一個哨兵POOL_BOUNDARY時,將EmptyPoolPlaceholder存入 TLS 中。
  2. 若是 TLS 存儲了EmptyPoolPlaceholder時,在建立好page以後,會先推入一個POOL_BOUNDARY,而後再將加入自動釋放池的對象推入。

5.5 objc_autoreleasePoolPop

在自動釋放池所在做用域結束時,會調用objc_autoreleasePoolPop,對自動釋放池中的對象進行釋放。

static inline void pop(void *token) 
{
    AutoreleasePoolPage *page;
    id *stop;
    // 若是是空池佔位符,要清空整個自動釋放池
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        if (hotPage()) {
            // 若是存在 hotPage ,則找到 coldPage 的起點 從新 pop
            pop(coldPage()->begin());
        } else {
            // 未使用過的釋放池,置空 TLS 中存放的 hotPage
            setHotPage(nil);
        }
        return;
    }
    page = pageForPointer(token);
    stop = (id *)token;
    if (*stop != POOL_BOUNDARY) {
        // 在 stop 不爲 POOL_BOUNDARY 的狀況下 只多是 coldPage()->begin()
        if (stop == page->begin()  &&  !page->parent) {
        } else {
            // 若是不是 POOL_BOUNDARY 也不是 coldPage()->begin() 則報錯
            return badPop(token);
        }
    }
    if (PrintPoolHiwat) printHiwat();
    // 釋放 stop 後面的全部對象
    page->releaseUntil(stop);
    
    // 清除後續節點 page
    if (page->child) {
        // 若是當前 page 沒有達到半滿,則幹掉全部後續 page
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        // 若是當前 page 達到半滿以上,則保留下一頁
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}
複製代碼

上面這段代碼邏輯:

  1. 檢查入參是否爲空池佔位符EMPTY_POOL_PLACEHOLDER,若是是則繼續判斷是否hotPage存在,若是hotPage存在則將釋放的終點改爲coldPage()->begin(),若是hotPage不存在,則置空 TLS 存儲中的hotPage
  2. 檢查stop既不是POOL_BOUNDARY也不是coldPage()->begin()的狀況將報錯。
  3. 清空自動釋放池中stop以後的全部對象。
  4. 判斷當前page若是沒有達到半滿,則幹掉全部後續全部 page,若是超過半滿則只保留下一個page

5.6 add 和 releaseUntil

整個AutoreleasePoolPage就是一個堆棧,經過AutoreleasePoolPageadd方法將對象加入自動釋放池:

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // 複製指針
    *next++ = obj;  // 將 obj 存入 next 指向的內存地址後,next 向後移指向下一個空地址
    protect();
    return ret;
}
複製代碼

這段邏輯很是簡單,add方法將新加入的對象存入棧頂指針next指向的地址中,而後指向下一個位置。

下面是AutoreleasePoolPage出棧的實現:

void releaseUntil(id *stop) 
{
    // 當 next 和 stop 不是指向同一塊內存地址,則繼續執行
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();
        // 若是 hotPage 空了,則設置上一個 page 爲 hotPage
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }
        page->unprotect();
        id obj = *--page->next; // page->next-- 後指向當前棧頂元素
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 將棧頂元素內存清空
        page->protect();
        if (obj != POOL_BOUNDARY) {
            objc_release(obj); // 棧頂元素引用計數 - 1
        }
    }
    setHotPage(this);
}
複製代碼

這段代碼大體邏輯:

  1. 判斷棧頂指針nextstop不是指向同一塊內存地址時,繼續出棧。
  2. 判斷當前page若是被清空,則繼續清理鏈表中的上一個page
  3. 出棧,棧頂指針往下移,清空棧頂內存。
  4. 若是當前出棧的不是POOL_BOUNDARY,則調用objc_release引用計數 - 1 。

體會

經過閱讀 objc4 源碼,將之前關於 ARC 的知識串聯起來,其中對細節的實現原理理解得更加透徹。

若是你以爲本文還不錯的話,能夠到原文 【理解 ARC 實現原理】 給個 ✨ 。

參考

相關文章
相關標籤/搜索