国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

iOS--weak的底層

這篇具有很好參考價(jià)值的文章主要介紹了iOS--weak的底層。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

怎么說(shuō)

在iOS開(kāi)發(fā)過(guò)程中,會(huì)經(jīng)常使用到一個(gè)修飾詞weak,使用場(chǎng)景大家都比較清晰,避免出現(xiàn)對(duì)象之間的強(qiáng)強(qiáng)引用而造成對(duì)象不能被正常釋放最終導(dǎo)致內(nèi)存泄露的問(wèn)題。weak 關(guān)鍵字的作用是弱引用,所引用對(duì)象的計(jì)數(shù)器不會(huì)加1,并在引用對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為 nil。

weak 初探

下面的一段代碼是我們?cè)陂_(kāi)發(fā)中常見(jiàn)的weak的使用


    NSObject *object = [NSObject alloc];
    id __weak objc = object;

如果在此打斷點(diǎn)跟蹤匯編信息,可以發(fā)現(xiàn)底層庫(kù)調(diào)了objc_initWeak函數(shù)
iOS--weak的底層,ios,cocoa,macos
iOS--weak的底層,ios,cocoa,macos
那么我們來(lái)看一下objc_initWeak 方法的實(shí)現(xiàn)代碼是怎么樣的呢?

objc_initWeak方法

如下是objc_initWeak 方法的底層源碼

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

該方法的兩個(gè)參數(shù)locationnewObj。

location :__weak指針的地址,存儲(chǔ)指針的地址,這樣便可以在最后將其指向的對(duì)象置為nil。
newObj :所引用的對(duì)象。即例子中的obj 。

從上面的代碼可以看出objc_initWeak方法只是一個(gè)深層次函數(shù)調(diào)用的入口,在該方法內(nèi)部調(diào)用了storeWeak 方法。下面我們來(lái)看下storeWeak 方法的實(shí)現(xiàn)代碼。

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

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

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

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems.
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) { // 如果weak ptr之前弱引用過(guò)一個(gè)obj,則將這個(gè)obj所對(duì)應(yīng)的SideTable取出,賦值給oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil; // 如果weak ptr之前沒(méi)有弱引用過(guò)一個(gè)obj,則oldTable = nil
    }
    if (haveNew) { // 如果weak ptr要weak引用一個(gè)新的obj,則將該obj對(duì)應(yīng)的SideTable取出,賦值給newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil; // 如果weak ptr不需要引用一個(gè)新obj,則newTable = nil
    }
    
    // 加鎖操作,防止多線程中競(jìng)爭(zhēng)沖突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // location 應(yīng)該與 oldObj 保持一致,如果不同,說(shuō)明當(dāng)前的 location 已經(jīng)處理過(guò) oldObj 可是又被其他線程所修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized())  // 如果cls還沒(méi)有初始化,先初始化,再嘗試設(shè)置weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls; // 這里記錄一下previouslyInitializedClass, 防止改if分支再次進(jìn)入

            goto retry; // 重新獲取一遍newObj,這時(shí)的newObj應(yīng)該已經(jīng)初始化過(guò)了
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用過(guò)別的對(duì)象oldObj,則調(diào)用weak_unregister_no_lock,在oldObj的weak_entry_t中移除該weak_ptr地址
    }

    // Assign new value, if any.
    if (haveNew) { // 如果weak_ptr需要弱引用新的對(duì)象newObj
        // (1) 調(diào)用weak_register_no_lock方法,將weak ptr的地址記錄到newObj對(duì)應(yīng)的weak_entry_t中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
        
        // (2) 更新newObj的isa的weakly_referenced bit標(biāo)志位
        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // (3)*location 賦值,也就是將weak ptr直接指向了newObj??梢钥吹剑@里并沒(méi)有將newObj的引用計(jì)數(shù)+1
        *location = (id)newObj; // 將weak ptr指向object
    }
    else {
        // No new value. The storage is not changed.
    }
    
    // 解鎖,其他線程可以訪問(wèn)oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj; // 返回newObj,此時(shí)的newObj與剛傳入時(shí)相比,weakly-referenced bit位置1
}

storeWeak 方法的實(shí)現(xiàn)代碼雖然有些長(zhǎng),但是并不難以理解。下面我們來(lái)分析下該方法的實(shí)現(xiàn)。

  • storeWeak方法實(shí)際上是接收了5個(gè)參數(shù),分別是haveOld、haveNew和crashIfDeallocating ,這三個(gè)參數(shù)都是以模板的方式傳入的,是三個(gè)bool類型的參數(shù)。 分別表示weak指針之前是否指向了一個(gè)弱引用,weak指針是否需要指向一個(gè)新的引用,若果被弱引用的對(duì)象正在析構(gòu),此時(shí)再弱引用該對(duì)象是否應(yīng)該crash。
  • 該方法維護(hù)了oldTable 和newTable分別表示舊的引用弱表和新的弱引用表,它們都是SideTable的hash表。
  • 如果weak指針之前指向了一個(gè)弱引用,則會(huì)調(diào)用weak_unregister_no_lock 方法將舊的weak指針地址移除。
  • 如果weak指針需要指向一個(gè)新的引用,則會(huì)調(diào)用weak_register_no_lock 方法將新的weak指針地址添加到弱引用表中。
  • 調(diào)用setWeaklyReferenced_nolock 方法修改weak新引用的對(duì)象的bit標(biāo)志位

storeWeak函數(shù)接收了5個(gè)參數(shù),它們分別是:

id *location: 這是一個(gè)指向id類型指針的參數(shù),用于存儲(chǔ)弱引用的對(duì)象。
objc_object *newObj: 這是一個(gè)指向objc_object類型的指針,表示要進(jìn)行弱引用的新對(duì)象。
HaveOld haveOld: 這是一個(gè)枚舉類型參數(shù),用于指示是否有舊的弱引用對(duì)象。
HaveNew haveNew: 這是一個(gè)枚舉類型參數(shù),用于指示是否有新的弱引用對(duì)象。
CrashIfDeallocating crashIfDeallocating: 這是一個(gè)枚舉類型參數(shù),用于指示在釋放對(duì)象時(shí)是否崩潰。
注意:HaveOld,HaveNew和CrashIfDeallocating都是枚舉類型,它們?cè)谀0鍏?shù)中用于在編譯時(shí)選擇性地執(zhí)行不同的代碼路徑。

那么這個(gè)方法中的重點(diǎn)也就是weak_unregister_no_lockweak_register_no_lock 這兩個(gè)方法。而這兩個(gè)方法都是操作的SideTable 這樣一個(gè)結(jié)構(gòu)的變量,那么我們需要先來(lái)了解下SideTable

SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void reset() { slock.reset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

SideTable的定義很清晰,有三個(gè)成員:

spinlock_t slock : 自旋鎖,用于上鎖/解鎖 SideTable。
RefcountMap refcnts :用來(lái)存儲(chǔ)OC對(duì)象的引用計(jì)數(shù)的 hash表(僅在未開(kāi)啟isa優(yōu)化或在isa優(yōu)化情況下isa_t的引用計(jì)數(shù)溢出時(shí)才會(huì)用到)。
weak_table_t weak_table : 存儲(chǔ)對(duì)象弱引用指針的hash表。是OC中weak功能實(shí)現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)。

weak_table_t

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

weak_entries: hash數(shù)組,用來(lái)存儲(chǔ)弱引用對(duì)象的相關(guān)信息weak_entry_t
num_entries: hash數(shù)組中的元素個(gè)數(shù)
mask:hash數(shù)組長(zhǎng)度-1,會(huì)參與hash計(jì)算。(注意,這里是hash數(shù)組的長(zhǎng)度,而不是元素個(gè)數(shù)。比如,數(shù)組長(zhǎng)度可能是64,而元素個(gè)數(shù)僅存了2個(gè))
max_hash_displacement:可能會(huì)發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯(cuò)誤(hash表中的沖突次數(shù)絕不會(huì)超過(guò)改值)

weak_table_t是一個(gè)典型的hash結(jié)構(gòu)。weak_entries是一個(gè)動(dòng)態(tài)數(shù)組,用來(lái)存儲(chǔ)weak_entry_t類型的元素,這些元素實(shí)際上就是OC對(duì)象的弱引用信息。

weak_entry_t

weak_entry_t的結(jié)構(gòu)也是一個(gè)hash結(jié)構(gòu),其存儲(chǔ)的元素是弱引用對(duì)象指針的指針, 通過(guò)操作指針的指針,就可以使得weak 引用的指針在對(duì)象析構(gòu)后,指向nil。

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 被弱引用的對(duì)象
    
    // 引用該對(duì)象的對(duì)象列表,聯(lián)合。 引用個(gè)數(shù)小于4,用inline_referrers數(shù)組。 用個(gè)數(shù)大于4,用動(dòng)態(tài)數(shù)組weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;                      // 弱引用該對(duì)象的對(duì)象指針地址的hash數(shù)組
            uintptr_t        out_of_line_ness : 2;           // 是否使用動(dòng)態(tài)hash數(shù)組標(biāo)記位
            uintptr_t        num_refs : PTR_MINUS_2;         // hash數(shù)組中的元素個(gè)數(shù)
            uintptr_t        mask;                           // hash數(shù)組長(zhǎng)度-1,會(huì)參與hash計(jì)算。(注意,這里是hash數(shù)組的長(zhǎng)度,而不是元素個(gè)數(shù)。比如,數(shù)組長(zhǎng)度可能是64,而元素個(gè)數(shù)僅存了2個(gè))素個(gè)數(shù))。
            uintptr_t        max_hash_displacement;          // 可能會(huì)發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯(cuò)誤(hash表中的沖突次數(shù)絕不會(huì)超過(guò)改值)
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent) // 構(gòu)造方法,里面初始化了靜態(tài)數(shù)組
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

可以看到在weak_entry_t 的結(jié)構(gòu)定義中有聯(lián)合體,在聯(lián)合體的內(nèi)部有定長(zhǎng)數(shù)組inline_referrers[WEAK_INLINE_COUNT]和動(dòng)態(tài)數(shù)組weak_referrer_t *referrers兩種方式來(lái)存儲(chǔ)弱引用對(duì)象的指針地址。通過(guò)out_of_line()這樣一個(gè)函數(shù)方法來(lái)判斷采用哪種存儲(chǔ)方式。當(dāng)弱引用該對(duì)象的指針數(shù)目小于等于WEAK_INLINE_COUNT時(shí),使用定長(zhǎng)數(shù)組。當(dāng)超過(guò)WEAK_INLINE_COUNT時(shí),會(huì)將定長(zhǎng)數(shù)組中的元素轉(zhuǎn)移到動(dòng)態(tài)數(shù)組中,并之后都是用動(dòng)態(tài)數(shù)組存儲(chǔ)。

到這里我們已經(jīng)清楚了弱引用表的結(jié)構(gòu)是一個(gè)hash結(jié)構(gòu)的表,Key是所指對(duì)象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象的地址)數(shù)組。那么接下來(lái)看看這個(gè)弱引用表是怎么維護(hù)這些數(shù)據(jù)的。

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

    // 如果referent為nil 或 referent 采用了TaggedPointer計(jì)數(shù)方式,直接返回,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 確保被引用的對(duì)象可用(沒(méi)有在析構(gòu),同時(shí)應(yīng)該支持weak引用)
    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);
    }
    // 正在析構(gòu)的對(duì)象,不能夠被弱引用
    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;
        }
    }

    // now remember it and where it is being stored
    // 在 weak_table中找到referent對(duì)應(yīng)的weak_entry,并將referrer加入到weak_entry中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) { // 如果能找到weak_entry,則講referrer插入到weak_entry中
        append_referrer(entry, referrer); 	// 將referrer插入到weak_entry_t的引用數(shù)組中
    } 
    else { // 如果找不到,就新建一個(gè)
        weak_entry_t new_entry(referent, referrer);  
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

這個(gè)方法需要傳入四個(gè)參數(shù),它們代表的意義如下:

weak_table:weak_table_t 結(jié)構(gòu)類型的全局的弱引用表。
referent_id:weak指針。
*referrer_id:weak指針地址。
crashIfDeallocating :若果被弱引用的對(duì)象正在析構(gòu),此時(shí)再弱引用該對(duì)象是否應(yīng)該crash。

從上面的代碼我么可以知道該方法主要的做了如下幾個(gè)方便的工作。

如果referent為nil 或 referent 采用了TaggedPointer計(jì)數(shù)方式,直接返回,不做任何操作。
如果對(duì)象正在析構(gòu),則拋出異常。
如果對(duì)象不能被weak引用,直接返回nil。
如果對(duì)象沒(méi)有再析構(gòu)且可以被weak引用,則調(diào)用weak_entry_for_referent 方法根據(jù)弱引用對(duì)象的地址從弱引用表中找到對(duì)應(yīng)的weak_entry,如果能夠找到則調(diào)用append_referrer 方法向其中插入weak指針地址。否則新建一個(gè)weak_entry。

weak_entry_for_referent取元素

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;

    size_t begin = hash_pointer(referent) & weak_table->mask;  // 這里通過(guò) & weak_table->mask的位操作,來(lái)確保index不會(huì)越界
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries); // 觸發(fā)bad weak table crash
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) { // 當(dāng)hash沖突超過(guò)了可能的max hash 沖突時(shí),說(shuō)明元素沒(méi)有在hash表中,返回nil 
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

append_referrer添加元素

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { // 如果weak_entry 尚未使用動(dòng)態(tài)數(shù)組,走這里
        // Try to insert inline.
        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的位置已經(jīng)存滿了,則要轉(zhuǎn)型為referrers,做動(dòng)態(tài)數(shù)組。
        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[I];
        }
        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;
    }

    // 對(duì)于動(dòng)態(tài)數(shù)組的附加處理:
    assert(entry->out_of_line()); // 斷言: 此時(shí)一定使用的動(dòng)態(tài)數(shù)組

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果動(dòng)態(tài)數(shù)組中元素個(gè)數(shù)大于或等于數(shù)組位置總空間的3/4,則擴(kuò)展數(shù)組空間為當(dāng)前長(zhǎng)度的一倍
        return grow_refs_and_insert(entry, new_referrer); // 擴(kuò)容,并插入
    }
    
    // 如果不需要擴(kuò)容,直接插入到weak_entry中
    // 注意,weak_entry是一個(gè)哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
    
    // 細(xì)心的人可能注意到了,這里weak_entry_t 的hash算法和 weak_table_t的hash算法是一樣的,同時(shí)擴(kuò)容/減容的算法也是一樣的
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 確保了 begin的位置只能大于或等于 數(shù)組的長(zhǎng)度
    size_t index = begin;  // 初始的hash index
    size_t hash_displacement = 0;  // 用于記錄hash沖突的次數(shù),也就是hash再位移的次數(shù)
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;  // index + 1, 移到下一個(gè)位置,再試一次能否插入。(這里要考慮到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因?yàn)閿?shù)組每次都是*2增長(zhǎng),即8, 16, 32,對(duì)應(yīng)動(dòng)態(tài)數(shù)組空間長(zhǎng)度-1的mask,也就是前面的取值。)
        if (index == begin) bad_weak_table(entry); // index == begin 意味著數(shù)組繞了一圈都沒(méi)有找到合適位置,這時(shí)候一定是出了什么問(wèn)題。
    }
    if (hash_displacement > entry->max_hash_displacement) { // 記錄最大的hash沖突次數(shù), max_hash_displacement意味著: 我們嘗試至多max_hash_displacement次,肯定能夠找到object對(duì)應(yīng)的hash位置
        entry->max_hash_displacement = hash_displacement;
    }
    // 將ref存入hash數(shù)組,同時(shí),更新元素個(gè)數(shù)num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

這段代碼首先確定是使用定長(zhǎng)數(shù)組還是動(dòng)態(tài)數(shù)組,如果是使用定長(zhǎng)數(shù)組,則直接將weak指針地址添加到數(shù)組即可,如果定長(zhǎng)數(shù)組已經(jīng)用盡,則需要將定長(zhǎng)數(shù)組中的元素轉(zhuǎn)存到動(dòng)態(tài)數(shù)組中。

weak_unregister_no_lock移除引用

如果weak指針之前指向了一個(gè)弱引用,則會(huì)調(diào)用weak_unregister_no_lock方法將舊的weak指針地址移除。

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))) { // 查找到referent所對(duì)應(yīng)的weak_entry_t
        remove_referrer(entry, referrer);  // 在referent所對(duì)應(yīng)的weak_entry_t的hash數(shù)組中,移除referrer
       
        // 移除元素之后, 要檢查一下weak_entry_t的hash數(shù)組是否已經(jīng)空了
        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_t的hash數(shù)組已經(jīng)空了,則需要將weak_entry_t從weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }

  1. 首先,它會(huì)在weak_table中找出referent對(duì)應(yīng)的weak_entry_t
  2. 在weak_entry_t中移除referrer
  3. 移除元素后,判斷此時(shí)weak_entry_t中是否還有元素 (empty==true?)
  4. 如果此時(shí)weak_entry_t已經(jīng)沒(méi)有元素了,則需要將weak_entry_t從weak_table中移除

到這里為止就是對(duì)于一個(gè)對(duì)象做weak引用時(shí)底層做的事情,用weak引用對(duì)象后引用計(jì)數(shù)并不會(huì)加1,當(dāng)對(duì)象釋放時(shí),所有weak引用它的指針又是如何自動(dòng)設(shè)置為nil的呢?

dealloc

當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),底層會(huì)調(diào)用_objc_rootDealloc方法對(duì)對(duì)象進(jìn)行釋放,而在_objc_rootDealloc方法里面會(huì)調(diào)用rootDealloc方法。如下是rootDealloc方法的代碼實(shí)現(xiàn)。

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

首先判斷對(duì)象是否是Tagged Pointer,如果是則直接返回。
如果對(duì)象是采用了優(yōu)化的isa計(jì)數(shù)方式,且同時(shí)滿足對(duì)象沒(méi)有被weak引用!isa.weakly_referenced、沒(méi)有關(guān)聯(lián)對(duì)象!isa.has_assoc 、沒(méi)有自定義的C++析構(gòu)方法!isa.has_cxx_dtor、沒(méi)有用到SideTable來(lái)引用計(jì)數(shù)!isa.has_sidetable_rc則直接快速釋放。
如果不能滿足2中的條件,則會(huì)調(diào)用object_dispose 方法。

object_dispose

object_dispose 方法很簡(jiǎn)單,主要是內(nèi)部調(diào)用了objc_destructInstance方法。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

上面這一段代碼很清晰,如果有自定義的C++析構(gòu)方法,則調(diào)用C++析構(gòu)函數(shù)。如果有關(guān)聯(lián)對(duì)象,則移除關(guān)聯(lián)對(duì)象并將其自身從Association Manager的map中移除。調(diào)用clearDeallocating 方法清除對(duì)象的相關(guān)引用。

clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

clearDeallocating中有兩個(gè)分支,先判斷對(duì)象是否采用了優(yōu)化isa引用計(jì)數(shù),如果沒(méi)有的話則需要清理對(duì)象存儲(chǔ)在SideTable中的引用計(jì)數(shù)數(shù)據(jù)。如果對(duì)象采用了優(yōu)化isa引用計(jì)數(shù),則判斷是否有使用SideTable的輔助引用計(jì)數(shù)(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合這兩種情況中一種的,調(diào)用clearDeallocating_slow 方法。

clearDeallocating_slow

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this]; // 在全局的SideTables中,以this指針為key,找到對(duì)應(yīng)的SideTable
    table.lock();
    if (isa.weakly_referenced) { // 如果obj被弱引用
        weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中對(duì)this進(jìn)行清理工作
    }
    if (isa.has_sidetable_rc) { // 如果采用了SideTable做引用計(jì)數(shù)
        table.refcnts.erase(this); // 在SideTable的引用計(jì)數(shù)中移除this
    }
    table.unlock();
}

在這里我們關(guān)心的是weak_clear_no_lock 方法。這里調(diào)用了weak_clear_no_lock來(lái)做weak_table的清理工作。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-602688.html

weak_clear_no_lock

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // 找到referent在weak_table中對(duì)應(yīng)的weak_entry_t
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    // 找出weak引用referent的weak 指針地址數(shù)組以及數(shù)組長(zhǎng)度
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // 取出每個(gè)weak ptr的地址
        if (referrer) {
            if (*referrer == referent) { // 如果weak ptr確實(shí)weak引用了referent,則將weak ptr設(shè)置為nil,這也就是為什么weak 指針會(huì)自動(dòng)設(shè)置為nil的原因
                *referrer = nil;
            }
            else if (*referrer) { // 如果所存儲(chǔ)的weak ptr沒(méi)有weak 引用referent,這可能是由于runtime代碼的邏輯錯(cuò)誤引起的,報(bào)錯(cuò)
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry); // 由于referent要被釋放了,因此referent的weak_entry_t也要移除出weak_table
}

總結(jié)

  • 1、weak的原理在于底層維護(hù)了一張weak_table_t結(jié)構(gòu)的hash表,key是所指對(duì)象的地址,value是weak指針的地址數(shù)組。
  • 2、weak 關(guān)鍵字的作用是弱引用,所引用對(duì)象的計(jì)數(shù)器不會(huì)加1,并在引用對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為 nil。
  • 3、對(duì)象釋放時(shí),調(diào)用clearDeallocating函數(shù)根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除,最后清理對(duì)象的記錄。
  • 4、文章中介紹了SideTable、weak_table_t、weak_entry_t這樣三個(gè)結(jié)構(gòu),它們之間的關(guān)系如下圖所示。iOS--weak的底層,ios,cocoa,macos

到了這里,關(guān)于iOS--weak的底層的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Charles證書(shū)過(guò)期解決方法macos/ios

    Charles證書(shū)過(guò)期解決方法macos/ios

    今天心血來(lái)潮打開(kāi)Charles想試試看抓包手機(jī)APP(ios),結(jié)果發(fā)現(xiàn)各種x和提示ssl錯(cuò)誤。開(kāi)始以為是和魔法的代理沖突或者ip變了,捯飭很久后發(fā)現(xiàn)web的也報(bào)錯(cuò)。 然后搜了一會(huì)原因發(fā)現(xiàn)時(shí)證書(shū)過(guò)期了 1、搜索“鑰匙串訪問(wèn)”,直接搜索“charles”,找到打叉的名稱,直接刪掉 2、打開(kāi)

    2024年02月03日
    瀏覽(23)
  • MacOS 14 系統(tǒng) XCode15、 Flutter 開(kāi)發(fā) IOS

    MacOS 14 系統(tǒng) XCode15、 Flutter 開(kāi)發(fā) IOS

    MacOS14 Sonoma 安裝 Flutter 開(kāi)發(fā)環(huán)境 MacOS 系統(tǒng) Flutter開(kāi)發(fā)Android 環(huán)境配置 MacOS 系統(tǒng) Flutter開(kāi)發(fā)IOS 環(huán)境配置??????? 前面我們已經(jīng)在MacOS14 M3芯片上安裝好 Flutter環(huán)境,包括開(kāi)發(fā)工具 VsCode 、Android Stuiod,那么flutter如何開(kāi)發(fā)IOS呢? 我們知道IOS開(kāi)發(fā)語(yǔ)言為 objcet-c或者 swift,F(xiàn)lutter是

    2024年02月03日
    瀏覽(23)
  • 鴻蒙開(kāi)發(fā)者的必修課:Linux底層IO方式深度剖析 ?

    鴻蒙開(kāi)發(fā)者的必修課:Linux底層IO方式深度剖析 ?

    博主貓頭虎的技術(shù)世界 ?? 歡迎來(lái)到貓頭虎的博客 — 探索技術(shù)的無(wú)限可能! 專欄鏈接 : ?? 精選專欄 : 《面試題大全》 — 面試準(zhǔn)備的寶典! 《IDEA開(kāi)發(fā)秘籍》 — 提升你的IDEA技能! 《100天精通Golang》 — Go語(yǔ)言學(xué)習(xí)之旅! 領(lǐng)域矩陣 : ?? 貓頭虎技術(shù)領(lǐng)域矩陣 : 深入探索

    2024年02月19日
    瀏覽(27)
  • macOS Sonoma編譯OpenCV源碼輸出IOS平臺(tái)庫(kù)

    macOS Sonoma編譯OpenCV源碼輸出IOS平臺(tái)庫(kù)

    1.macOS下載并編譯OpenCV源碼:? 克隆源碼: 主倉(cāng):?git clone https://github.com/opencv/opencv.git 擴(kuò)展倉(cāng):? git clone https://github.com/opencv/opencv_contrib.git ? ?編譯xcode源碼需要CMake與XCode命令行工具 確認(rèn)已安裝CMake ?確認(rèn)已安裝XCode ?安裝xcode command line tools 確認(rèn)系統(tǒng)已安裝python環(huán)境

    2024年02月10日
    瀏覽(18)
  • macos搭建appium-iOS自動(dòng)化測(cè)試環(huán)境

    macos搭建appium-iOS自動(dòng)化測(cè)試環(huán)境

    目錄 準(zhǔn)備工作 安裝必需的軟件 安裝appium 安裝XCode 下載WDA工程 配置WDA工程 搭建appium+wda自動(dòng)化環(huán)境 第一步:?jiǎn)?dòng)通過(guò)xcodebuild命令啟動(dòng)wda服務(wù) 分享一下如何在mac電腦上搭建一個(gè)完整的appium自動(dòng)化測(cè)試環(huán)境 前期需要準(zhǔn)備的設(shè)備和賬號(hào): mac電腦一臺(tái) iphone一臺(tái) 蘋(píng)果開(kāi)發(fā)者賬號(hào)一

    2024年02月13日
    瀏覽(22)
  • uniapp打包之配置MacOS虛擬機(jī)生成iOS打包證書(shū)

    uniapp打包之配置MacOS虛擬機(jī)生成iOS打包證書(shū)

    uniapp是一款跨端開(kāi)發(fā)框架,可用于快速開(kāi)發(fā)iOS、Android、H5等多端應(yīng)用。本文將詳細(xì)介紹如何實(shí)現(xiàn)uniapp開(kāi)發(fā)的iOS應(yīng)用打包。 一、下載蘋(píng)果原版鏡像文件 點(diǎn)擊此處下載 二、安裝VMware uniapp打包iOS應(yīng)用需要生成相應(yīng)證書(shū)和P2文件,這些都需要用到IOS環(huán)境,這里我是使用的是MacOS虛擬機(jī)

    2024年02月12日
    瀏覽(18)
  • 終極解決Flutter項(xiàng)目運(yùn)行ios項(xiàng)目報(bào)錯(cuò)Without CocoaPods, plugins will not work on iOS or macOS.

    終極解決Flutter項(xiàng)目運(yùn)行ios項(xiàng)目報(bào)錯(cuò)Without CocoaPods, plugins will not work on iOS or macOS.

    最近在開(kāi)發(fā)Flutter項(xiàng)目,運(yùn)行ios環(huán)境的時(shí)候報(bào)錯(cuò)沒(méi)有CocoaPods,安卓環(huán)境可以正常運(yùn)行,當(dāng)時(shí)一臉懵逼,網(wǎng)上搜索了一下,有給我講原理的,還有讓我安裝這插件那插件的,最終把電腦搞得卡死,還沒(méi)有解決我的問(wèn)題,其實(shí)很多人和我一樣只想解決問(wèn)題,而不是廢話一大堆的文章

    2024年01月22日
    瀏覽(24)
  • 如何建設(shè)一個(gè)用于編譯 iOS App 的 macOS 云服務(wù)器集群?

    作者:京東零售 葉萌 現(xiàn)代軟件開(kāi)發(fā)一般會(huì)借助 CI/CD 來(lái)提升代碼質(zhì)量、加快發(fā)版速度、自動(dòng)化重復(fù)的事情,iOS App 只能在 mac 機(jī)器上編譯,CI/CD 工具因此需要有一個(gè) macOS 云服務(wù)器集群來(lái)執(zhí)行 iOS App 的編譯。 今天就來(lái)談?wù)勅绾谓ㄔO(shè) macOS 云服務(wù)器集群 最簡(jiǎn)單的方式就是購(gòu)買一批

    2023年04月25日
    瀏覽(21)
  • iOS自動(dòng)化測(cè)試方案(一):MacOS虛擬機(jī)保姆級(jí)安裝Xcode教程

    iOS自動(dòng)化測(cè)試方案(一):MacOS虛擬機(jī)保姆級(jí)安裝Xcode教程

    一、環(huán)境準(zhǔn)備 1、下載VMware虛擬機(jī)的殼子,安裝并注冊(cè)軟件(可以百度注冊(cè)碼),最新版本:v17 2、下MacOS系統(tǒng)iOS鏡像文件,用于vmware虛擬機(jī)安裝,當(dāng)前鏡像最新版本:v11.6 二、基礎(chǔ)軟件 1、MacOS系統(tǒng)找到App Store,這點(diǎn)同iphone操作,搜索Xcode點(diǎn)擊安裝 2、不出意外的話還是出意外了,

    2024年02月07日
    瀏覽(18)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包