成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

lavor / 3740人閱讀

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

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

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

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

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

弱引用表的數(shù)據(jù)結(jié)構(gòu)。

解釋一下自動(dòng)釋放池中的 Hot Page 和 Cold Page。

如果上述幾個(gè)問(wèn)題你已經(jīng)非常清楚,那本文可能對(duì)你的幫助有限,但如果你對(duì)這幾個(gè)問(wèn)題還存有疑問(wèn),那相信本文一定能解答你的疑問(wèn)。

一、Clang

在 Objective-C 中,對(duì)象的引用關(guān)系由引用修飾符來(lái)決定,如__strong、__weak、__autorelease等等,編譯器會(huì)根據(jù)不同的修飾符生成不同邏輯的代碼來(lái)管理內(nèi)存。

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

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

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

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

在命令行輸入命令后你可以在文件夾下面發(fā)現(xiàn)main.ll,它的內(nèi)容如下:

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
}

雖然內(nèi)容有點(diǎn)多,但是仔細(xì)分析下來(lái)大概就是以下內(nèi)容:

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

obj_msgSend(NSObject, @selector(new))非常好理解,就是新建一個(gè)對(duì)象,而objc_storeStrong是 objc4 庫(kù)中的方法,具體邏輯如下:

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

上面的代碼按順序做了以下 4 件事:

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

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

    指針指向 obj。

    原來(lái)指向的對(duì)象引用計(jì)數(shù) - 1。

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

二、 isa

在分析 ARC 相關(guān)源碼之前,需要對(duì) isa 有一定了解,其中存儲(chǔ)了一些非常重要的信息,下面是 isa 的結(jié)構(gòu)組成:

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用優(yōu)化的isa指針
         uintptr_t has_assoc         : 1;//->是否包含關(guān)聯(lián)對(duì)象
         uintptr_t has_cxx_dtor      : 1;//->是否設(shè)置了析構(gòu)函數(shù),如果沒(méi)有,釋放對(duì)象更快
         uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->類的指針
         uintptr_t magic             : 6;//->固定值,用于判斷是否完成初始化
         uintptr_t weakly_referenced : 1;//->對(duì)象是否被弱引用
         uintptr_t deallocating      : 1;//->對(duì)象是否正在銷毀
         uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存儲(chǔ)引用計(jì)數(shù)將要溢出的時(shí)候,借助Sidetable(散列表)存儲(chǔ)引用計(jì)數(shù),has_sidetable_rc設(shè)置成1
        uintptr_t extra_rc          : 19;  //->存儲(chǔ)引用計(jì)數(shù)
    };
};

其中nonpointer、weakly_referencedhas_sidetable_rcextra_rc都是 ARC 有直接關(guān)系的成員變量,其他的大多也有涉及到。

struct objc_object {
    isa_t isa;
};

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

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;
    cache_t cache; 方法實(shí)現(xiàn)緩存和 vtable
    class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};

objc_class繼承了objc_object,結(jié)構(gòu)如下:

isa:objc_object 指向類,objc_class 指向元類。

superclass:指向父類。

cache:存儲(chǔ)用戶消息轉(zhuǎn)發(fā)優(yōu)化的方法緩存和 vtable 。

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

三、 __strong 修飾符

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

3.1 __strong 修飾符的中間碼

接下來(lái)繼續(xù)將 Objective-C 代碼轉(zhuǎn)成 LLVM 中間碼,這次我們?cè)囈幌?b>__strong修飾符:

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

中間碼(后面的中間碼為了方便理解就刪除無(wú)用代碼):

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)建對(duì)象、引用計(jì)數(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

接下來(lái)我們通過(guò)分析 objc4 庫(kù)的源碼來(lái)了解objc_retainobjc_release的內(nèi)部邏輯。先看objc_retain具體實(shí)現(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 個(gè)小分支:

TaggedPointer:值存在指針內(nèi),直接返回。

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

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

未溢出時(shí),isa.extra_rc + 1 完事。

溢出時(shí),將 isa.extra_rc 中一半值轉(zhuǎn)移至sidetable中,然后將isa.has_sidetable_rc設(shè)置為true,表示使用了sidetable來(lái)計(jì)算引用次數(shù)。

3.3 objc_release

繼續(xù)看objc_release具體實(shí)現(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) {
        	// 調(diào)用本函數(shù)處理下溢
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

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

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

    // 如果已經(jīng)是釋放中,拋個(gè)過(guò)度釋放錯(cuò)誤
    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;
}

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

TaggedPointer: 直接返回 false。

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

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

未下溢: extra_rc--。

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

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

3.4 rootRetainCount

objc_object::rootRetainCount方法是用來(lái)計(jì)算引用計(jì)數(shù)的。通過(guò)前面rootRetainrootRelease的源碼分析可以看出引用計(jì)數(shù)會(huì)分別存在isa.extra_rcsidetable。中,這一點(diǎn)在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 時(shí)代有一句話叫 誰(shuí)創(chuàng)建誰(shuí)釋放 ,意思是由開(kāi)發(fā)者通過(guò)allocnew、copymutableCopy等方法創(chuàng)建的對(duì)象,需要開(kāi)發(fā)者手動(dòng)釋放,而由其他方法創(chuàng)建并返回的對(duì)象返回給用戶后也不需要開(kāi)發(fā)者釋放,比如說(shuō)由[NSMutableArray array]方法創(chuàng)建的數(shù)組,這樣的對(duì)象默認(rèn)由自動(dòng)釋放池管理。進(jìn)入 ARC 時(shí)代后,針對(duì)返回的對(duì)象編譯器也做了一些特殊處理,具體通過(guò)下面的內(nèi)容來(lái)理解其中奧妙。

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

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

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

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

    在執(zhí)行objc_autoreleaseReturnValue時(shí),優(yōu)化流程將一個(gè)標(biāo)志位存儲(chǔ)在 TLS (Thread Local Storage) 中后直接返回對(duì)象。

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

下面再通過(guò)源代碼來(lái)進(jìn)行分析,先看看objc_autoreleaseReturnValue具體實(shí)現(xiàn):

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

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0);
	// 檢查使用該函數(shù)的方法或調(diào)用方的的調(diào)用列表,如果緊接著執(zhí)行 objc_retainAutoreleasedReturnValue ,將不注冊(cè)到 autoreleasePool 中
    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
    	// 設(shè)置標(biāo)記 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);
}

// 取出標(biāo)記
static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}

objc_autoreleaseReturnValue代碼邏輯大概分為:

    檢查調(diào)用者方法里后面是否緊跟著調(diào)用了objc_retainAutoreleasedReturnValue

    保存 ReturnAtPlus1 至 TLS 中。

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

下面是objc_retainAutoreleasedReturnValue的源碼分析:

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

    return objc_retain(obj);
}

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

objc_retainAutoreleasedReturnValue代碼邏輯大概分為:

    取出 TLS 中標(biāo)記。

    重置 TLS 中標(biāo)記至 ReturnAtPlus0 。

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

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

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

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

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

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

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

四、__weak 修飾符

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

4.1 __weak 修飾符的中間碼

首先將下面代碼轉(zhuǎn)換成中間碼:

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

下面是轉(zhuǎn)化提煉后的中間碼:

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 對(duì)象后并沒(méi)有使用到,所以在objc_initWeak后,立即釋放調(diào)用了objc_releaseobjc_destroyWeak方法。

weak1Function:該方法中obj是強(qiáng)引用,obj1是弱引用,objc_initWeak、 objc_destroyWeak先后成對(duì)調(diào)用,對(duì)應(yīng)著弱引用變量的初始化和釋放方法。

weak2Function:和weak1Function不同之處是使用了弱引用變量obj1,在使用弱引用變量之前,編譯器創(chuàng)建了一個(gè)臨時(shí)的強(qiáng)引用對(duì)象,在用完后立即釋放。

4.2 objc_initWeak 和 objc_destroyWeak

4.2.1 objc_initWeak 和 objc_destroyWeak

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

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


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

通過(guò)源代碼可以發(fā)現(xiàn)最終都是通過(guò)storeWeak來(lái)實(shí)現(xiàn)各自邏輯的,在查看storeWeak實(shí)現(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
}; // 操作正在釋放中的對(duì)象是否 Crash

4.2.2 storeWeak

接下來(lái)繼續(xù)看storeWeak的實(shí)現(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 中取出存儲(chǔ)弱引用表的 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) {
        // 如果該對(duì)象類還未初始化則進(jìn)行初始化
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 創(chuàng)建一個(gè)非元類,并且初始化,會(huì)調(diào)用 +initialize 函數(shù)
            SideTable::unlockTwo(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    
    // 如果有舊值,清除舊值對(duì)應(yīng)的弱引用表
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 如果賦予了新值,注冊(cè)新值對(duì)應(yīng)的弱引用表
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);

        // 設(shè)置 isa 標(biāo)志位 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中,利用對(duì)象本身地址進(jìn)行位運(yùn)算后得到對(duì)應(yīng)下標(biāo),取得該對(duì)象的弱引用表。SideTables是一個(gè) 64 個(gè)元素長(zhǎng)度的散列表,發(fā)生碰撞時(shí),可能一個(gè)SideTable中存在多個(gè)對(duì)象共享一個(gè)弱引用表。

    如果有分配新值,則檢查新值對(duì)應(yīng)的類是否初始化過(guò),如果沒(méi)有,則就地初始化。

    如果 location 有指向其他舊值,則將舊值對(duì)應(yīng)的弱引用表進(jìn)行注銷。

    如果分配了新值,將新值注冊(cè)到對(duì)應(yīng)的弱引用表中。將isa.weakly_referenced設(shè)置為true,表示該對(duì)象是有弱引用變量,釋放時(shí)要去清空弱引用表。

4.2.3 weak_register_no_lock 和 weak_unregister_no_lock

在上面的代碼中使用到weak_register_no_lockweak_unregister_no_lock來(lái)進(jìn)行弱引用表的注冊(cè)和注銷,繼續(xù)查看這兩個(gè)方法的實(shí)現(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; // 被引用的對(duì)象
    objc_object **referrer = (objc_object **)referrer_id; // 弱引用變量
    
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
    // 檢查當(dāng)前對(duì)象沒(méi)有在釋放中
    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; // 每個(gè)對(duì)象對(duì)應(yīng)的一個(gè)弱引用記錄
    // 如果當(dāng)前表中有該對(duì)象的記錄則直接加入該 weak 表中對(duì)應(yīng)記錄
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    }
    else {
        // 沒(méi)有在 weak 表中找到對(duì)應(yīng)記錄,則新建一個(gè)記錄
        weak_entry_t new_entry(referent, referrer);
        // 查看 weak_table 表是否要擴(kuò)容
        weak_grow_maybe(weak_table);
        // 將記錄插入 weak 表中
        weak_entry_insert(weak_table, &new_entry);
    }
        
    return referent_id;
}

上面這段代碼主要邏輯:

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

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

    如果weak_table沒(méi)有找到對(duì)應(yīng)的entry,則新建一個(gè)entry,并將弱引用變量指針地址加入entry中。同時(shí)檢查weak_table是否需要擴(kuò)容。

下面是weak_unregister_no_lock代碼實(shí)現(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; // 被引用的對(duì)象
    objc_object **referrer = (objc_object **)referrer_id; // 弱引用變量

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 找到 weak 表中對(duì)應(yīng)記錄后,將引用從記錄中移除
        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;
                }
            }
        }
        // 如果當(dāng)前記錄為空則移除記錄
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

上面這段代碼主要邏輯:

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

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

4.2.4 weak_table

上面的代碼中使用weak_table保存被引用對(duì)象的entry,下面繼續(xù)通過(guò)分析weak_table的增刪查函數(shù)的具體實(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 對(duì)地址做位運(yùn)算得出哈希表下標(biāo)的方式
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 線性探測(cè),如果該下標(biāo)存儲(chǔ)的是其他對(duì)象,那往下移,直至找到正確的下標(biāo)。
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask; // 不能超過(guò) weak_table 最大長(zhǎng)度限制
        
        // 回到初始下標(biāo),異常報(bào)錯(cuò)
        if (index == begin) bad_weak_table(weak_table->weak_entries); 
        
        // 每次沖突下移 hash_displacement + 1,當(dāng)前位移不超過(guò)記錄在案的最大位移
        hash_displacement++; 
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

上述代碼就是weak_table查找entry的過(guò)程,也是哈希表尋址過(guò)程,使用線性探測(cè)的方法解決哈希沖突的問(wèn)題:

    通過(guò)被引用對(duì)象地址計(jì)算獲得哈希表下標(biāo)。

    檢查對(duì)應(yīng)下標(biāo)存儲(chǔ)的是不是我們要找到地址,如果是則返回該地址。

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

下面是weak_table插入entry的代碼實(shí)現(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);

	// 通過(guò)哈希算法得到下標(biāo)
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    
    // 判斷當(dāng)前下標(biāo)是否為空,如果不是繼續(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;
    }
}

和查過(guò)過(guò)程類似,weak_table插入entry的的步驟:

    通過(guò)被引用對(duì)象地址計(jì)算獲得哈希表下標(biāo)。

    檢查對(duì)應(yīng)下標(biāo)是否為空,如果不為空繼續(xù)往下查找,直至找到空位。

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

下面是weak_table移除entry的代碼實(shí)現(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 對(duì)象數(shù)量,并檢查是否可以縮減表容量
    weak_table->num_entries--;
    weak_compact_maybe(weak_table);
}

weak_table移除entry的的步驟:

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

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

4.2.5 entry 和 referrer

在弱引用表中entry對(duì)應(yīng)著被引用的對(duì)象,而referrer代表弱引用變量。每次被弱引用時(shí),都會(huì)將弱引用變量指針referrer加入entry中,而當(dāng)原對(duì)象被釋放時(shí),會(huì)將entry清空并移除。

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

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // inline_referrers 未超出時(shí),直接加入 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 的引用轉(zhuǎn)移只 new_referrers
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // 修改 entry 內(nèi)容及標(biāo)志位
        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());
    // 當(dāng)負(fù)載因子過(guò)高進(jìn)行擴(kuò)容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    // 根據(jù)地址計(jì)算下標(biāo)
    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;
    }
    // 找到合適下標(biāo)后存儲(chǔ)
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

entry的結(jié)構(gòu)和weak_table相似,都使用了哈希表,并且使用線性探測(cè)法尋找對(duì)應(yīng)位置。在此基礎(chǔ)上有一點(diǎn)不同的地方:

    entry有一個(gè)標(biāo)志位out_of_line,最初時(shí)該標(biāo)志位為false,entry使用的是一個(gè)有序數(shù)組inline_referrers的存儲(chǔ)結(jié)構(gòu)。

    當(dāng)inline_referrers的成員數(shù)量超過(guò)了WEAK_INLINE_COUNT,out_of_line標(biāo)志位變成true,開(kāi)始使用哈希表存儲(chǔ)結(jié)構(gòu)。每當(dāng)哈希表負(fù)載超過(guò) 3/4 時(shí)會(huì)進(jìn)行擴(kuò)容。

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

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        // 未超出 inline_referrers 時(shí)直接將對(duì)應(yīng)位置清空
        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ù)地址計(jì)算下標(biāo)
    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;
        }
    }
    // 找到后將對(duì)應(yīng)位置置空
    entry->referrers[index] = nil;
    entry->num_refs--;
}

entry移除referrer的步驟:

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

    out_of_linetrue時(shí),從哈希表中查找并移除。

4.2.6 dealloc

當(dāng)被引用的對(duì)象被釋放后,會(huì)去檢查isa.weakly_referenced標(biāo)志位,每個(gè)被弱引用的對(duì)象weakly_referenced標(biāo)志位都為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ù)指針獲取對(duì)應(yīng) weak_table
    SideTable& table = SideTables()[this];
    table.lock();
    // 判斷如果有被弱引用則清空該對(duì)象對(duì)應(yīng)的 entry
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 清空該對(duì)象存儲(chǔ)在 sidetable 中的引用計(jì)數(shù)
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

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

4.3 objc_loadWeakRetained

通過(guò)前面的中間碼分析可以得知,在使用弱引用變量之前,編譯器創(chuàng)建了一個(gè)臨時(shí)的強(qiáng)引用對(duì)象,以此保證使用時(shí)不會(huì)因?yàn)楸会尫艑?dǎo)致出錯(cuò),在用完后立即釋放。

下面看看如何對(duì)弱引用指針指向?qū)ο筮M(jìn)行強(qiáng)引用:

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

    SideTable *table;
    
 retry:
	// 得到弱引用指針指向?qū)ο?    obj = *location;
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj; // TaggedPointer 直接返回
    
    // 得到對(duì)應(yīng) weak_table
    table = &SideTables()[obj];
    
    // 如果被引用對(duì)象在此期間發(fā)生變化則重試
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // 類和超類沒(méi)有自定義 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;
            }
            // 調(diào)用自定義函數(shù)
            else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        }
        else {
            // 類未初始化,則初始化后回到 retry 重新執(zhí)行
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}

上面的代碼主要邏輯:

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

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

    如果使用了自定義retain方法,則調(diào)用自定義方法,在調(diào)用之前會(huì)先判斷該對(duì)象所屬類是否已經(jīng)初始化過(guò),如果沒(méi)有初始化會(huì)先進(jìn)行初始化然后再調(diào)用。

五、__autorelease 修飾符

在 ARC 環(huán)境下, __autorelease 修飾符可以將對(duì)象加入自動(dòng)釋放池中,由自動(dòng)釋放池管理釋放。

5.1 __autorelease 修飾符的中間碼

將下面的代碼轉(zhuǎn)換成中間碼查看編譯器的處理:

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

轉(zhuǎn)換后的中間碼:

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

通過(guò)上面的代碼可以分析出:

@autoreleasepool{}關(guān)鍵字通過(guò)編譯器轉(zhuǎn)換成objc_autoreleasePoolPushobjc_autoreleasePoolPop這一對(duì)方法。

__autoreleasing 修飾符轉(zhuǎn)換成objc_autorelease,將obj加入自動(dòng)釋放池中。

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

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

    使用objc_autorelease將對(duì)象加入自動(dòng)釋放池。

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

5.2 自動(dòng)釋放池的預(yù)備知識(shí)

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

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

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

coldPage:位于鏈表頭部的page,可能同時(shí)為hotPage。

POOL_BOUNDARYnil的宏定義,替代之前的哨兵對(duì)象POOL_SENTINEL,在自動(dòng)釋放池創(chuàng)建時(shí),在objc_autoreleasePoolPush中將其推入自動(dòng)釋放池中。在調(diào)用objc_autoreleasePoolPop時(shí),會(huì)將池中對(duì)象按順序釋放,直至遇到最近一個(gè)POOL_BOUNDARY時(shí)停止。

EMPTY_POOL_PLACEHOLDER:當(dāng)自動(dòng)釋放池中沒(méi)有推入過(guò)任何對(duì)象時(shí),這個(gè)時(shí)候推入一個(gè)POOL_BOUNDARY,會(huì)先將EMPTY_POOL_PLACEHOLDER存儲(chǔ)在 TLS 中作為標(biāo)識(shí)符,并且此次并不推入POOL_BOUNDARY。等再次有對(duì)象被推入自動(dòng)釋放池時(shí),檢查在 TLS 中取出該標(biāo)識(shí)符,這個(gè)時(shí)候再推入POOL_BOUNDARY。

next:指向AutoreleasePoolPage指向棧頂空位的指針,每次加入新的元素都會(huì)往上移動(dòng)。

5.3 objc_autoreleasePoolPush

objc_autoreleasePoolPush方法的代碼實(shí)現(xiàn):

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        // 測(cè)試狀態(tài)下,每個(gè)自動(dòng)釋放池都會(huì)新建一個(gè) page
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
    	// 推一個(gè) POOL_BOUNDARY 入棧,表示該釋放池的起點(diǎn)
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

objc_autoreleasePoolPush方法其實(shí)就是向自動(dòng)釋放池推入一個(gè)POOL_BOUNDARY,作為該autoreleasepool的起點(diǎn)。autoreleaseFast方法的具體邏輯將在后面分析autorelease方法時(shí)再進(jìn)行分析。

5.4 autorelease

下面看加入自動(dòng)釋放池方法autorelease的代碼實(shí)現(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)建一個(gè) Page 后,加入新 Page 中
        return autoreleaseFullPage(obj, page);
    } else {
        // 沒(méi)有 hotPage 直接新建一個(gè) Page,并加入 Page 中
        return autoreleaseNoPage(obj);
    }
}

上面這段代碼邏輯:

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

如果hotPage存在且已滿,調(diào)用autoreleaseFullPage。

如果hotPage不存在,調(diào)用autoreleaseNoPage。

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

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);
    // 找到一個(gè)未滿的 page , 未找到則新建一個(gè) page ,設(shè)置成 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é)點(diǎn),如果有直接使用后繼節(jié)點(diǎn)。

    如果沒(méi)有后繼節(jié)點(diǎn),則新建一個(gè)AutoreleasePoolPage。

    將對(duì)象加入獲取到的page,并將其設(shè)置為hotPage,其實(shí)就是存入 TLS 中共享。

下面開(kāi)始分析autoreleaseNoPage方法代碼實(shí)現(xiàn):

id *autoreleaseNoPage(id obj)
{
    // 執(zhí)行 No page 表示目前還沒(méi)有釋放池,或者有一個(gè)空占位符池,但是還沒(méi)有加入對(duì)象
    assert(!hotPage());
    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        // 如果是空占位符池,需要加入一個(gè)釋放池邊界
        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 則設(shè)置空池占位符
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        return setEmptyPoolPlaceholder();
    }
    // We are pushing an object or a non-placeholder d pool.
    // 初始化一個(gè) page 并設(shè)置 hotPage
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    // 插入釋放池邊界
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    // 對(duì)象加入釋放池
    return page->add(obj);
}

autoreleaseNoPage只有在自動(dòng)釋放池還沒(méi)有page時(shí)調(diào)用,主要邏輯:

    如果當(dāng)前自動(dòng)釋放池推入的是一個(gè)哨兵POOL_BOUNDARY時(shí),將EmptyPoolPlaceholder存入 TLS 中。

    如果 TLS 存儲(chǔ)了EmptyPoolPlaceholder時(shí),在創(chuàng)建好page之后,會(huì)先推入一個(gè)POOL_BOUNDARY,然后再將加入自動(dòng)釋放池的對(duì)象推入。

5.5 objc_autoreleasePoolPop

在自動(dòng)釋放池所在作用域結(jié)束時(shí),會(huì)調(diào)用objc_autoreleasePoolPop,對(duì)自動(dòng)釋放池中的對(duì)象進(jìn)行釋放。

static inline void pop(void *token) 
{
    AutoreleasePoolPage *page;
    id *stop;
    // 如果是空池占位符,要清空整個(gè)自動(dòng)釋放池
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        if (hotPage()) {
            // 如果存在 hotPage ,則找到 coldPage 的起點(diǎn) 重新 pop
            pop(coldPage()->begin());
        } else {
            // 未使用過(guò)的釋放池,置空 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() 則報(bào)錯(cuò)
            return badPop(token);
        }
    }
    if (PrintPoolHiwat) printHiwat();
    // 釋放 stop 后面的所有對(duì)象
    page->releaseUntil(stop);
    
    // 清除后續(xù)節(jié)點(diǎn) page
    if (page->child) {
        // 如果當(dāng)前 page 沒(méi)有達(dá)到半滿,則干掉所有后續(xù) page
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        // 如果當(dāng)前 page 達(dá)到半滿以上,則保留下一頁(yè)
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

上面這段代碼邏輯:

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

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

    清空自動(dòng)釋放池中stop之后的所有對(duì)象。

    判斷當(dāng)前page如果沒(méi)有達(dá)到半滿,則干掉所有后續(xù)所有 page,如果超過(guò)半滿則只保留下一個(gè)page。

5.6 add 和 releaseUntil

整個(gè)AutoreleasePoolPage就是一個(gè)堆棧,通過(guò)AutoreleasePoolPageadd方法將對(duì)象加入自動(dòng)釋放池:

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // 復(fù)制指針
    *next++ = obj;  // 將 obj 存入 next 指向的內(nèi)存地址后,next 向后移指向下一個(gè)空地址
    protect();
    return ret;
}

這段邏輯非常簡(jiǎn)單,add方法將新加入的對(duì)象存入棧頂指針next指向的地址中,然后指向下一個(gè)位置。

下面是AutoreleasePoolPage出棧的實(shí)現(xiàn):

void releaseUntil(id *stop) 
{
    // 當(dāng) next 和 stop 不是指向同一塊內(nèi)存地址,則繼續(xù)執(zhí)行
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();
        // 如果 hotPage 空了,則設(shè)置上一個(gè) page 為 hotPage
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }
        page->unprotect();
        id obj = *--page->next; // page->next-- 后指向當(dāng)前棧頂元素
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 將棧頂元素內(nèi)存清空
        page->protect();
        if (obj != POOL_BOUNDARY) {
            objc_release(obj); // 棧頂元素引用計(jì)數(shù) - 1
        }
    }
    setHotPage(this);
}

這段代碼大致邏輯:

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

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

    出棧,棧頂指針往下移,清空棧頂內(nèi)存。

    如果當(dāng)前出棧的不是POOL_BOUNDARY,則調(diào)用objc_release引用計(jì)數(shù) - 1 。

體會(huì)

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

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

參考

Objective-C 高級(jí)編程

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/6716.html

相關(guān)文章

  • canvas仿芝麻信用分儀表盤(pán)

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

    ASCH 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<