国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

理解 ARC 實現(xiàn)原理

lavor / 3731人閱讀

摘要:是中管理引用計數(shù)的技術,幫助實現(xiàn)垃圾自動回收,具體實現(xiàn)的原理是由編譯器進行管理的,同時運行時庫協(xié)助編譯器輔助完成。本文主要內容由修飾符拓展開,分別延伸出引用計數(shù)弱引用表自動釋放池等實現(xiàn)原理。判斷使用了優(yōu)化處理則返回對象,否則引用計數(shù)。

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

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

在 ARC 下如何存儲引用計數(shù)?

[NSDictionary dictionary]方法創(chuàng)建的對象在 ARC 中有什么不同之處。

弱引用表的數(shù)據(jù)結構。

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

如果上述幾個問題你已經非常清楚,那本文可能對你的幫助有限,但如果你對這幾個問題還存有疑問,那相信本文一定能解答你的疑問。

一、Clang

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

首先看看 Clang 在其中具體起到哪些作用,我們可以在命令行使用下面的命令來將 Objective-C 代碼轉成 LLVM 中間碼:

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

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

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

在命令行輸入命令后你可以在文件夾下面發(fā)現(xiàn)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 件事:

    檢查輸入的 obj 地址 和指針指向的地址是否相同。

    持有對象,引用計數(shù) + 1 。

    指針指向 obj。

    原來指向的對象引用計數(shù) - 1。

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

二、 isa

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

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用優(yōu)化的isa指針
         uintptr_t has_assoc         : 1;//->是否包含關聯(lián)對象
         uintptr_t has_cxx_dtor      : 1;//->是否設置了析構函數(shù),如果沒有,釋放對象更快
         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存儲引用計數(shù)將要溢出的時候,借助Sidetable(散列表)存儲引用計數(shù),has_sidetable_rc設置成1
        uintptr_t extra_rc          : 19;  //->存儲引用計數(shù)
    };
};

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

struct objc_object {
    isa_t isa;
};

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

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;
    cache_t cache; 方法實現(xiàn)緩存和 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:存儲用戶消息轉發(fā)優(yōu)化的方法緩存和 vtable 。

bits:class_rw_t 和 class_ro_t ,保存了方法、協(xié)議、屬性等列表和一些標志位。

三、 __strong 修飾符

在 MRC 時代 Retain 修飾符將會使被引用的對象引用計數(shù) + 1 ,在 ARC 中 __strong 修飾符作為其替代者,具體起到什么樣的作用?我們可以通過 Clang 將 Objective-C 代碼轉成 LLVM 來分析其中原理。

3.1 __strong 修飾符的中間碼

接下來繼續(xù)將 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);
}

上面代碼一看就是非常常規(guī)的操作,創(chuàng)建對象、引用計數(shù) + 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具體實現(xiàn):

id objc_retain(id obj) {
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

繼續(xù)往下查看最終定位到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)) {
            // 未優(yōu)化的 isa 部分
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            // 
            if (tryRetain) return sidetable_tryRetain() ");

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

TaggedPointer:值存在指針內,直接返回。

!newisa.nonpointer:未優(yōu)化的 isa ,使用sidetable_retain()

newisa.nonpointer:已優(yōu)化的 isa , 這其中又分 extra_rc 溢出和未溢出的兩種情況。

未溢出時,isa.extra_rc + 1 完事。

溢出時,將 isa.extra_rc 中一半值轉移至sidetable中,然后將isa.has_sidetable_rc設置為true,表示使用了sidetable來計算引用次數(shù)。

3.3 objc_release

繼續(xù)看objc_release具體實現(xiàn),最終定位到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)) {
            // 未優(yōu)化 isa
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            // 入?yún)⑹欠褚獔?zhí)行 Dealloc 函數(shù),如果為 true 則執(zhí)行 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) {
        	// 調用本函數(shù)處理下溢
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

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

        if (borrowed > 0) {
			// extra_rc 是計算額外的引用計數(shù),0 即表示被引用一次
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
                                                
            // 保存失敗,恢復現(xiàn)場,重試                                    
            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 計數(shù) == 0 的就直接釋放

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

    if (slowpath(sideTableLocked)) sidetable_unlock();

	// 執(zhí)行 SEL_dealloc 事件
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

這么一長串代碼,將其分解后和 rootRetain 邏輯類似:

TaggedPointer: 直接返回 false。

!nonpointer: 未優(yōu)化的 isa 執(zhí)行 sidetable_release。

nonpointer:已優(yōu)化的 isa ,分下溢和未下溢兩種情況。

未下溢: extra_rc--。

下溢:從 sidetable 中借位給 extra_rc 達到半滿,如果無法借位則說明引用計數(shù)歸零需要進行釋放。其中借位時可能保存失敗會不斷重試。

到這里可以知道 引用計數(shù)分別保存在isa.extra_rcsidetable中,當isa.extra_rc溢出時,將一半計數(shù)轉移至sidetable中,而當其下溢時,又會將計數(shù)轉回。當二者都為空時,會執(zhí)行釋放流程

3.4 rootRetainCount

objc_object::rootRetainCount方法是用來計算引用計數(shù)的。通過前面rootRetainrootRelease的源碼分析可以看出引用計數(shù)會分別存在isa.extra_rcsidetable。中,這一點在rootRetainCount方法中也得到了體現(xiàn)。

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);
    // 優(yōu)化的 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;
    }
	// 未優(yōu)化返回 sidetable_retainCount
    sidetable_unlock();
    return sidetable_retainCount();
}

3.5 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue

在 MRC 時代有一句話叫 誰創(chuàng)建誰釋放 ,意思是由開發(fā)者通過allocnewcopymutableCopy等方法創(chuàng)建的對象,需要開發(fā)者手動釋放,而由其他方法創(chuàng)建并返回的對象返回給用戶后也不需要開發(fā)者釋放,比如說由[NSMutableArray array]方法創(chuàng)建的數(shù)組,這樣的對象默認由自動釋放池管理。進入 ARC 時代后,針對返回的對象編譯器也做了一些特殊處理,具體通過下面的內容來理解其中奧妙。

首先將下方創(chuàng)建數(shù)組的代碼轉成中間碼:

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);
}

相對于用戶創(chuàng)建的對象,[NSMutableArray array]方法創(chuàng)建返回的對象轉換后多出了一個objc_retainAutoreleasedReturnValue方法。這涉及到一個最優(yōu)化處理:

    為了節(jié)省了一個將對象注冊到autoreleasePool的操作,在執(zhí)行objc_autoreleaseReturnValue時,根據(jù)查看后續(xù)調用的方法列表是否包含objc_retainAutoreleasedReturnValue方法,以此判斷是否走優(yōu)化流程。

    在執(zhí)行objc_autoreleaseReturnValue時,優(yōu)化流程將一個標志位存儲在 TLS (Thread Local Storage) 中后直接返回對象。

    執(zhí)行后續(xù)方法objc_retainAutoreleasedReturnValue時檢查 TLS 的標志位判斷是否處于優(yōu)化流程,如果處于優(yōu)化流程中則直接返回對象,并且將 TLS 的狀態(tài)還原。

下面再通過源代碼來進行分析,先看看objc_autoreleaseReturnValue具體實現(xiàn):

id objc_autoreleaseReturnValue(id obj) {
	// 如果走優(yōu)化程序則直接返回對象
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
	// 否則還是走自動釋放池
    return objc_autorelease(obj);
}

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0);
	// 檢查使用該函數(shù)的方法或調用方的的調用列表,如果緊接著執(zhí)行 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代碼邏輯大概分為:

    檢查調用者方法里后面是否緊跟著調用了objc_retainAutoreleasedReturnValue

    保存 ReturnAtPlus1 至 TLS 中。

    使用了優(yōu)化處理則返回對象,否則加入自動釋放池。

下面是objc_retainAutoreleasedReturnValue的源碼分析:

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

    return objc_retain(obj);
}

static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {
	// 取出標記后返回
    ReturnDisposition disposition = getReturnDisposition();
    // 還原至未優(yōu)化狀態(tài)
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

objc_retainAutoreleasedReturnValue代碼邏輯大概分為:

    取出 TLS 中標記。

    重置 TLS 中標記至 ReturnAtPlus0 。

    判斷使用了優(yōu)化處理則返回對象,否則引用計數(shù) + 1。

通過分析源碼可以得知下面這段代碼的優(yōu)化流程和未優(yōu)化流程是有挺大的區(qū)別的:

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

最終優(yōu)化流程相當于:

id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_release(obj);

而未優(yōu)化流程相當于:

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

四、__weak 修飾符

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

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,在使用弱引用變量之前,編譯器創(chuàng)建了一個臨時的強引用對象,在用完后立即釋放。

4.2 objc_initWeak 和 objc_destroyWeak

4.2.1 objc_initWeak 和 objc_destroyWeak

下面是objc_initWeakobjc_destroyWeak的代碼實現(xiàn):

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
	// 該地址沒有值,正賦予新值,如果正在釋放將會 crash
    return storeWeak
        (location, (objc_object*)newObj);
}


void objc_destroyWeak(id *location) {
	// 該地址有值,沒有賦予新值,如果正在釋放不 crash
    (void)storeWeak
        (location, nil);
}

通過源代碼可以發(fā)現(xiàn)最終都是通過storeWeak來實現(xiàn)各自邏輯的,在查看storeWeak實現(xiàn)之前,我們要先了解一下它的模板參數(shù)的含義:

storeWeak (location, (objc_object*)newObj);

其中DontHaveOldDoHaveNewDoCrashIfDeallocating都是模板參數(shù),具體含義如下:

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

4.2.2 storeWeak

接下來繼續(xù)看storeWeak的實現(xiàn):

template 
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(oldTable, newTable);
    // location 指向的值發(fā)生改變,則重新執(zhí)行獲取 oldObj
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(oldTable, newTable);
        goto retry;
    }

    // 如果有新值
    if (haveNew  &&  newObj) {
        // 如果該對象類還未初始化則進行初始化
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 創(chuàng)建一個非元類,并且初始化,會調用 +initialize 函數(shù)
            SideTable::unlockTwo(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(oldTable, newTable);
    return (id)newObj;
}

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

    從全局的哈希表SideTables中,利用對象本身地址進行位運算后得到對應下標,取得該對象的弱引用表。SideTables是一個 64 個元素長度的散列表,發(fā)生碰撞時,可能一個SideTable中存在多個對象共享一個弱引用表。

    如果有分配新值,則檢查新值對應的類是否初始化過,如果沒有,則就地初始化。

    如果 location 有指向其他舊值,則將舊值對應的弱引用表進行注銷。

    如果分配了新值,將新值注冊到對應的弱引用表中。將isa.weakly_referenced設置為true,表示該對象是有弱引用變量,釋放時要去清空弱引用表。

4.2.3 weak_register_no_lock 和 weak_unregister_no_lock

在上面的代碼中使用到weak_register_no_lockweak_unregister_no_lock來進行弱引用表的注冊和注銷,繼續(xù)查看這兩個方法的實現(xiàn):

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);
    }
    
    // 如果正在釋放中,則根據(jù) crashIfDeallocating 判斷是否觸發(fā) 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;
}

上面這段代碼主要邏輯:

    檢查是否正在被釋放中,如果是則根據(jù)crashIfDeallocating判斷是否觸發(fā) crash 。

    檢查weak_table中是否有被引用對象對應的entry,如果有則直接將弱引用變量指針地址加入該entry中。

    如果weak_table沒有找到對應的entry,則新建一個entry,并將弱引用變量指針地址加入entry中。同時檢查weak_table是否需要擴容。

下面是weak_unregister_no_lock代碼實現(xiàn):

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);
        }
    }
}

上面這段代碼主要邏輯:

    weak_table中根據(jù)找到被引用對象對應的entry,然后將弱引用變量指針referrerentry中移除。

    移除弱引用變量指針referrer之后,檢查entry是否為空,如果為空將其從weak_table中移除。

4.2.4 weak_table

上面的代碼中使用weak_table保存被引用對象的entry,下面繼續(xù)通過分析weak_table的增刪查函數(shù)的具體實現(xiàn):

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的過程,也是哈希表尋址過程,使用線性探測的方法解決哈希沖突的問題:

    通過被引用對象地址計算獲得哈希表下標。

    檢查對應下標存儲的是不是我們要找到地址,如果是則返回該地址。

    如果不是則繼續(xù)往下找,直至找到。在下移的過程中,下標不能超過weak_table最大長度,同時hash_displacement不能超過記錄的max_hash_displacement最大哈希位移。max_hash_displacement是所有插入操作時記錄的最大哈希位移,如果超過了,那肯定是出錯了。

下面是weak_table插入entry的代碼實現(xiàn):

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;
    
    // 判斷當前下標是否為空,如果不是繼續(xù)往下尋址空位
    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的的步驟:

    通過被引用對象地址計算獲得哈希表下標。

    檢查對應下標是否為空,如果不為空繼續(xù)往下查找,直至找到空位。

    將弱引用變量指針存入空位,同時更新weak_table的當前成員數(shù)量num_entries和最大哈希位移max_hash_displacement

下面是weak_table移除entry的代碼實現(xiàn):

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 對象數(shù)量,并檢查是否可以縮減表容量
    weak_table->num_entries--;
    weak_compact_maybe(weak_table);
}

weak_table移除entry的的步驟:

    釋放entry和其中的弱引用變量。

    更新 weak_table 對象數(shù)量,并檢查是否可以縮減表容量

4.2.5 entry 和 referrer

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

下面看往entry中添加referrer的具體實現(xiàn):

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 數(shù)量,則執(zhí)行下面代碼
        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);
    }
    // 根據(jù)地址計算下標
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 該下表位置下不為空,發(fā)生 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相似,都使用了哈希表,并且使用線性探測法尋找對應位置。在此基礎上有一點不同的地方:

    entry有一個標志位out_of_line,最初時該標志位為falseentry使用的是一個有序數(shù)組inline_referrers的存儲結構。

    inline_referrers的成員數(shù)量超過了WEAK_INLINE_COUNTout_of_line標志位變成true,開始使用哈希表存儲結構。每當哈希表負載超過 3/4 時會進行擴容。

繼續(xù)看從entry移除referrer的具體實現(xiàn):

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.
", 
                     old_referrer);
        objc_weak_error();
        return;
    }
    // 超出 inline_referrers 的邏輯
    
    // 根據(jù)地址計算下標
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 發(fā)生哈希沖突繼續(xù)往后查找
    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.
", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    // 找到后將對應位置置空
    entry->referrers[index] = nil;
    entry->num_refs--;
}

entry移除referrer的步驟:

    out_of_linefalse時,從有序數(shù)組inline_referrers中查找并移除。

    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));
	// 根據(jù)指針獲取對應 weak_table
    SideTable& table = SideTables()[this];
    table.lock();
    // 判斷如果有被弱引用則清空該對象對應的 entry
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 清空該對象存儲在 sidetable 中的引用計數(shù)
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

從上面的代碼可以看出,在對象釋執(zhí)行dealloc函數(shù)時,會檢查isa.weakly_referenced標志位,然后判斷是否要清理weak_table中的entry

4.3 objc_loadWeakRetained

通過前面的中間碼分析可以得知,在使用弱引用變量之前,編譯器創(chuàng)建了一個臨時的強引用對象,以此保證使用時不會因為被釋放導致出錯,在用完后立即釋放。

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

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];
    
    // 如果被引用對象在此期間發(fā)生變化則重試
    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;
            }
            // 調用自定義函數(shù)
            else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        }
        else {
            // 類未初始化,則初始化后回到 retry 重新執(zhí)行
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}

上面的代碼主要邏輯:

    通過弱引用指向的對象,獲取弱引用表,并且將其上鎖,防止在此期間被清除。

    判斷是否包含自定義retain方法,如果沒有,則使用默認rootTryRetain方法,使引用計數(shù) + 1 。

    如果使用了自定義retain方法,則調用自定義方法,在調用之前會先判斷該對象所屬類是否已經初始化過,如果沒有初始化會先進行初始化然后再調用。

五、__autorelease 修飾符

在 ARC 環(huán)境下, __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加入自動釋放池中。

從上面的分析可以看出,編譯器對自動釋放池的處理邏輯大致分成:

    objc_autoreleasePoolPush作為自動釋放池作用域的第一個函數(shù)。

    使用objc_autorelease將對象加入自動釋放池。

    objc_autoreleasePoolPop作為自動釋放池作用域的最后一個函數(shù)。

5.2 自動釋放池的預備知識

在繼續(xù)往下看之前,我們需要先了解一些關于自動釋放池的相關知識:

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

hotPage:是當前正在使用的page,操作都是在hotPage上完成,一般處于鏈表末端或者倒數(shù)第二個位置。存儲在 TLS 中,可以理解為一個每個線程共享一個自動釋放池鏈表。

coldPage:位于鏈表頭部的page,可能同時為hotPage

POOL_BOUNDARYnil的宏定義,替代之前的哨兵對象POOL_SENTINEL,在自動釋放池創(chuàng)建時,在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方法的代碼實現(xiàn):

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        // 測試狀態(tài)下,每個自動釋放池都會新建一個 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的代碼實現(xiàn):

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

繼續(xù)往下看:

static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage();
    
    if (page && !page->full()) {
        // 有 hotPage 且不滿 直接加入棧中
        return page->add(obj);
    } else if (page) {
        // hotPage 已滿 先創(chuàng)建一個 Page 后,加入新 Page 中
        return autoreleaseFullPage(obj, page);
    } else {
        // 沒有 hotPage 直接新建一個 Page,并加入 Page 中
        return autoreleaseNoPage(obj);
    }
}

上面這段代碼邏輯:

如果hotPage存在且未滿,則直接推入hotPage

如果hotPage存在且已滿,調用autoreleaseFullPage

如果hotPage不存在,調用autoreleaseNoPage

往下繼續(xù)分析autoreleaseFullPage的實現(xiàn):

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已滿的情況下執(zhí)行,具體邏輯如下:

    查看hotPage是否有后繼節(jié)點,如果有直接使用后繼節(jié)點。

    如果沒有后繼節(jié)點,則新建一個AutoreleasePoolPage

    將對象加入獲取到的page,并將其設置為hotPage,其實就是存入 TLS 中共享。

下面開始分析autoreleaseNoPage方法代碼實現(xiàn):

id *autoreleaseNoPage(id obj)
{
    // 執(zhí)行 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時調用,主要邏輯:

    如果當前自動釋放池推入的是一個哨兵POOL_BOUNDARY時,將EmptyPoolPlaceholder存入 TLS 中。

    如果 TLS 存儲了EmptyPoolPlaceholder時,在創(chuàng)建好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);
    
    // 清除后續(xù)節(jié)點 page
    if (page->child) {
        // 如果當前 page 沒有達到半滿,則干掉所有后續(xù) page
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        // 如果當前 page 達到半滿以上,則保留下一頁
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

上面這段代碼邏輯:

    檢查入?yún)⑹欠駷榭粘卣嘉环?b>EMPTY_POOL_PLACEHOLDER,如果是則繼續(xù)判斷是否hotPage存在,如果hotPage存在則將釋放的終點改成coldPage()->begin(),如果hotPage不存在,則置空 TLS 存儲中的hotPage

    檢查stop既不是POOL_BOUNDARY也不是coldPage()->begin()的情況將報錯。

    清空自動釋放池中stop之后的所有對象。

    判斷當前page如果沒有達到半滿,則干掉所有后續(xù)所有 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出棧的實現(xiàn):

void releaseUntil(id *stop) 
{
    // 當 next 和 stop 不是指向同一塊內存地址,則繼續(xù)執(zhí)行
    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); // 棧頂元素引用計數(shù) - 1
        }
    }
    setHotPage(this);
}

這段代碼大致邏輯:

    判斷棧頂指針nextstop不是指向同一塊內存地址時,繼續(xù)出棧。

    判斷當前page如果被清空,則繼續(xù)清理鏈表中的上一個page

    出棧,棧頂指針往下移,清空棧頂內存。

    如果當前出棧的不是POOL_BOUNDARY,則調用objc_release引用計數(shù) - 1 。

體會

通過閱讀 objc4 源碼,將以前關于 ARC 的知識串聯(lián)起來,其中對細節(jié)的實現(xiàn)原理理解得更加透徹。

如果你覺得本文還不錯的話,可以到原文 【理解 ARC 實現(xiàn)原理】 給個 ? 。

參考

Objective-C 高級編程

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6716.html

相關文章

  • canvas仿芝麻信用分儀表盤

    摘要:,這是一個仿支付寶芝麻信用分的一個,其實就是一個動畫儀表盤。首先,上原圖這個是在下支付寶上的截圖,分低各位見笑了。 hi,這是一個仿支付寶芝麻信用分的一個canvas,其實就是一個動畫儀表盤。 首先, 上原圖: showImg(https://segmentfault.com/img/bVbn3D6?w=750&h=1334); 這個是在下支付寶上的截圖,分低各位見笑了。然后看下我用c...

    ASCH 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<