OC_散列表

113 阅读4分钟

本文内容

sideTable - 散列表
__weak

上文说过,引用计数在存储时判断当前 isa指针 是否是 nonpointerIsa,不是就会在 sidetable 中存储引用计数。若是nonpointerIsa,则存储在 extrc_rc 中,当 extrc_rc 存储不下时,会借位 sideTable 进行存储,并标记 has_sidetable_rc 为 yes。

sideTable - 散列表

SideTable& table = SideTables()[this];
struct SideTable {
    spinlock_t slock;           //os_unfire_lock
    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 forceReset() { slock.forceReset(); }

    // 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 主要包含一把spinlock锁、引用计数表弱引用表
  • spinlock_t:使用中会进行频繁对其调用,所以系统会使用多张 StripedMap 来分散压力
class StripedMap {
    //在真机上维护了8张表,模拟器上 64 张,
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
    ...
    //方法`indexForPointer`通过对象的地址,来确定是在array中的第几个元素,进而拿到哈希值。
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }
public:
    //运算符`[]`重载,获取对应的 sideTable
    //所以SideTable[this] = SideTable[indexForPointer(this)].value
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
    ...
}
  • RefcountMap:接受来自一般来自 extrc_rc 的引用计数,用 sidetable_addExtraRC_nolock 来存入表中
  • weak_table_t:弱引用表,key为对象的地址,value为 weak指针地址的数组
struct weak_table_t {
    weak_entry_t *weak_entries;       //哈希数组,存储弱引用对象相关信息,一个对象可以被多个弱引用对象引用
    size_t    num_entries;            //数组个数
    uintptr_t mask;
    uintptr_t max_hash_displacement;  //可能哈希冲突的次数
};

__weak

__weak 在底层维护了一张 哈希表,key为对象的地址,value为 weak指针地址的数组

int main(int ragc, const char * argv[]){
    @autoreleasepool{
        NSObject * o = [NSObject new];
        __weak typeof(o) weakOb = o;
    }
}
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

*location:weakOb 的指针地址
newObj:__weak 所引用的对象

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew, enum CrashIfDeallocating crashIfDeallocating>

HaveOld: weak指针是否指向了一个弱引用
HaveNew: weak指针是否需要指向一个新的引用
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) {
        oldObj = *location;                  //获取被弱引用的对象
        oldTable = &SideTables()[oldObj];    //获取存有 oldObj 的 表
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];    //获取 newObj 的表
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    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()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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;
            goto retry;
        }
    }
    // Clean up old value, if any. 清空 oldObj 的弱引用表
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejecte
        // Set is-weakly-referenced bit in refcount table.
        if (!_objc_isTaggedPointerOrNil(newObj)) {
            //标记 weakly_referenced == YES
            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);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

storeWeak流程:
首先获取 原弱引用表新对象的弱引用表
如果 weak指针 之前指向了一个弱引用,就会调用 weak_unregister_no_lockweak指针 地址移除
如果 weak指针 指向一个新的引用,就会调用 weak_register_no_lockweak指针地址 添加到对象对应的弱引用表
最后调用 setWeaklyReferenced_nolock,标记 isaweakly_referencedyes.

  • weak_register_no_lock:添加弱引用,
    • 判断是否为tagged pointer,是return
    • 不是,判断被 __weak 的对象能否被引用,不能抛出异常
    • 能被引用,就去寻找对象弱引用的表,通过 weak_entry_for_referent,在表中找到对应的 weak_entry_t,然后向表中插入 weak指针地址
    • 没找到 weak_entry_t,就新建一个
  • weak_unregister_no_lock:删除弱引用
    • 弱引用表中 找到对应的 weak_entry_t,在 weak_entry_t 中移除弱引用指针地址
    • 判断 weak_entry_t 中是否未空,是,就调用 weak_entry_remove 将 weak_entry_t 从 弱引用表中 移除
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        //当对象的弱引用个数 > 4,进行动态存储
        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;
        };
        //当对象的弱引用个数 <= 4,进行静态存储 weak指针地址
        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;
    }
    //构造函数,初始化 inline_referrers
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

weak_entry_t:哈希数组,存储弱引用对象相关信息。存储方式使用 union联合体 进行存储。会根据 out_of_line() 方法判断存储方式,当对象的弱引用个数 > 4,进行动态存储,当对象的弱引用个数 <= 4,进行静态存储 weak指针地址。

总结:nonpointerIsaextrc_rc 对引用计数进行存储时,extrc_rc 会分出一半的引用计数存储在 sideTable 中,其过程:SideTables->SideTable->weak_table_t最终找到被弱引用者的引用weak_entry_t

__weak 在底层维护了一张 哈希表,其底层方法也是对 sideTable表进行指针的添加或者删除操作。

  • sideTable 中三个参数:

    • spinlock_t 使用多张 StripedMap(StripedMap<spinlock_t>) 来分散访问压力。
    • RefcountMap 用来接受一半来自 extrc_rc 的引用计数。
    • weak_table_t 是个弱引用表,维护 weak_entry_t 哈希数组,存储弱引用对象相关信息。
  • 一个obj,对应了一个SideTable,但是一个SideTable,会对应多个obj。因为SideTable的数量在真机上存在8个,所以会有很多obj共用同一个SideTable。

  • 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿