iOS底層原理05 - 屬性關(guān)鍵字copy&weak&strong底層分析

上一篇: iOS底層原理04 - 類的結(jié)構(gòu)


Student類中添加分別由strong、copy、weak修飾的屬性:

在通過(guò)Clang編譯后的.cpp文件中,看到會(huì)生成三個(gè)對(duì)應(yīng)的setter方法:

唯獨(dú)在setAge的方法調(diào)用中,是通過(guò)objc_setProperty方法實(shí)現(xiàn)的。

1. copy

我們?cè)賝bjc-818.4源碼中搜索,看到了如下幾種方法:

objc_setProperty
  • setProperty

objc源碼中,搜索不到關(guān)于setProperty相關(guān)的調(diào)用,那么針對(duì)不同的修飾符,是如何跳轉(zhuǎn)到對(duì)應(yīng)的setProperty方法的呢?我們打開(kāi)llvm源碼來(lái)查找:

在llvm層,其實(shí)對(duì)copy屬性做了編譯器優(yōu)化,之后才會(huì)調(diào)用objc中的objc_setProperty_xxx方法。我們這里定義的age屬性使用了nonatomiccopy修飾,故name = "objc_setProperty_nonatomic_copy".

最后進(jìn)入reallySetProperty流程,這里可以通過(guò)斷點(diǎn)調(diào)試來(lái)看:

reallySetProperty
  • reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
  • 如果是copy或者mutableCopy并不會(huì)對(duì)新值進(jìn)行objc_retain操作,而是copyWithZone,返回一個(gè)不可變的對(duì)象
  • 若為atomic原子性的屬性,會(huì)在setter方法添加spinlock_t類型的自旋鎖來(lái)保證多線程寫入安全。

2. weak

.cpp中看到,使用weak修飾的對(duì)象,并不會(huì)走上面的objc_setProperty方法,而是走objc源碼中的objc_storeWeak來(lái)更新弱引用指針的指向

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;
    
    // 用地址作為唯一標(biāo)識(shí)來(lái)獲取新值和舊值鎖的位置
    // 通過(guò)地址來(lái)創(chuàng)建索引標(biāo)志,防止桶的重復(fù)
 retry:
    if (haveOld) {
        oldObj = *location;
        // 獲得以oldObj為索引存儲(chǔ)的SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 獲得以newObj為索引存儲(chǔ)的SideTable
        newTable = &SideTables()[newObj];
    } else {
        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;
    }
    // 避免弱引用機(jī)制和 +initialize 之間的死鎖
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 清除舊值
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // 設(shè)置新值
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
       
        if (!newObj->isTaggedPointerOrNil()) {
            // 弱引用位初始化操作      
            // 引用計(jì)數(shù)那張散列表的weak引用對(duì)象的引用計(jì)數(shù)中標(biāo)識(shí)為weak引用
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    callSetWeaklyReferenced((id)newObj);
    return (id)newObj;
}
  • location是指向weak指針的指針,因?yàn)橐薷膚eak指針
  • 上面storeWeak的過(guò)程,就是在解除與舊對(duì)象的關(guān)系,并與新對(duì)象建立聯(lián)系

runtime維護(hù)了一張weak表,用來(lái)存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針,從源碼中看到,這張表其實(shí)就是SideTables,這是一張hash表,key是對(duì)象的地址,value是weak指針的數(shù)組。

SideTables 散列表

SideTables結(jié)構(gòu)
  • 使用對(duì)象來(lái)獲取對(duì)應(yīng)的SideTable: &SideTables()[newObj]

  • spinlock_t slock : 自旋鎖,用于上鎖/解鎖 SideTable

  • RefcountMap refcnts :以DisguisedPtr<objc_object>為key的hash表,用來(lái)存儲(chǔ)OC對(duì)象的引用計(jì)數(shù)(僅在未開(kāi)啟isa優(yōu)化 或 在isa優(yōu)化情況下isa_t的引用計(jì)數(shù)溢出時(shí)才會(huì)用到)。

// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

RefcountMap是以DenseMap為模板創(chuàng)建的,三個(gè)參數(shù)分別為hash key類型,value類型,以及是否要在value==0時(shí)自動(dòng)釋放掉響應(yīng)的hash節(jié)點(diǎn),這里是true。

  • weak_table_t weak_table : 存儲(chǔ)對(duì)象弱引用指針的hash表。是OC weak功能實(shí)現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)。
struct weak_table_t {
    weak_entry_t *weak_entries; // hash數(shù)組 存儲(chǔ)弱引用對(duì)象的相關(guān)信息
    size_t    num_entries;      // hash數(shù)組的元素個(gè)數(shù)
    uintptr_t mask;             // hash數(shù)組長(zhǎng)度-1,參與hash計(jì)算
    uintptr_t max_hash_displacement;    // 可能會(huì)發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯(cuò)誤
};

weak_table_t是全局的弱引用表,將對(duì)象id存儲(chǔ)為鍵,將weak_entry_t存儲(chǔ)為它們的值。

在我們的App中,多個(gè)對(duì)象會(huì)重用同一個(gè)SideTable節(jié)點(diǎn),也就是說(shuō),weak_table會(huì)存儲(chǔ)多個(gè)對(duì)象的弱引用信息。因此在一個(gè)SideTable中,又會(huì)通過(guò)weak_table作為hash表再次分散存儲(chǔ)每一個(gè)對(duì)象的弱引用信息。

  • weak_entry_t
struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 被弱引用的對(duì)象
    // 兩種形式的聯(lián)合體 - 動(dòng)態(tài)數(shù)組weak_referrer_t和定長(zhǎng)數(shù)組weak_referrer_t
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    ...
};
  • DisguisedPtr<objc_object> referent:弱引用對(duì)象的指針摘要
  • union:聯(lián)合體,有兩種形式 - 動(dòng)態(tài)數(shù)組weak_referrer_t *referrers和定長(zhǎng)數(shù)組weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]。 用來(lái)存儲(chǔ)弱引用該對(duì)象的指針的指針。當(dāng)弱引用該對(duì)象的指針數(shù)目小于等于WEAK_INLINE_COUNT 4時(shí),為定長(zhǎng)數(shù)組;當(dāng)超過(guò)時(shí),會(huì)將定長(zhǎng)數(shù)組中的元素轉(zhuǎn)移到動(dòng)態(tài)數(shù)組中。

clearDeallocating

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

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);  // 從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)
    }
    table.unlock();
}

當(dāng)一個(gè)對(duì)象被釋放,調(diào)用clearDeallocating后會(huì)做如下操作:

  • 由當(dāng)前對(duì)象作為key找到SideTable
  • 將當(dāng)前對(duì)象轉(zhuǎn)為objc_object類型并作為key,從SideTableweak_table_t中取出weak_entry_t
  • 遍歷weak_referrer_t,將指向該對(duì)象的弱引用指針置為nil;
  • referrers數(shù)組移除,weak_table中hash數(shù)組的元素個(gè)數(shù)-1
  • table.refcnts.eraser() 從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)

3. strong

objc_storeStrong

若以strong修飾的屬性,會(huì)調(diào)用objc_storeStrong方法,進(jìn)行新值retain舊值release

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容