iOS内存管理08 -- weak的底层实现原理

  • 在阐述weak底层实现原理之前,首先介绍几个重要的数据结构;

SideTables散列表集合

  • 底层实现如下:
static StripedMap& SideTables() {
    return SideTablesMap.get();
}
template
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast>(this)[p]; 
    }

    // Shortcuts for StripedMaps of locks.
    void lockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.lock();
        }
    }

    void unlockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.unlock();
        }
    }

    void forceResetAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.forceReset();
        }
    }

    void defineLockOrder() {
        for (unsigned int i = 1; i < StripeCount; i++) {
            lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
        }
    }

    void precedeLock(const void *newlock) {
        // assumes defineLockOrder is also called
        lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
    }

    void succeedLock(const void *oldlock) {
        // assumes defineLockOrder is also called
        lockdebug_lock_precedes_lock(oldlock, &array[0].value);
    }

    const void *getLock(int i) {
        if (i < StripeCount) return &array[i].value;
        else return nil;
    }
    
#if DEBUG
    StripedMap() {
        // Verify alignment expectations.
        uintptr_t base = (uintptr_t)&array[0].value;
        uintptr_t delta = (uintptr_t)&array[1].value - base;
        ASSERT(delta % CacheLineSize == 0);
        ASSERT(base % CacheLineSize == 0);
    }
#else
    constexpr StripedMap() {}
#endif
};
  • SideTables顾名思义是存储sideTable散列表的集合,其类型为StripedMap属于哈希表;
  • StripeCount = 8表明在iOS或者非iOS模拟器上,最多会同时存在8张sideTable散列表;
  • static unsigned int indexForPointer(const void *p):根据对象获取对应的sideTable散列表 indexForPointer本质就是一个哈希函数,根据对象获取哈希索引值index;

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 forceReset() { slock.forceReset(); }

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

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
  • spinlock_t slock:线程锁保证散列表的访问是线程安全的;
  • RefcountMap refcnts:引用计数表,本质也是一个哈希表,类型如下:
typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;
  • 可以看出RefcountMap存储的键值对为:<对象,对象的引用计数值>
  • weak_table_t:弱引用表,是一个全局弱引用表,其结构如下所示:
/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
  • weak_entry_t *weak_entries:存储weak_entry_t结构体的数组;
  • size_t num_entries:表示存储weak_entries数组中存储weak_entry_t的数量;
  • uintptr_t mask:哈希数组的总长度减 1,会参与 hash 函数计算,保证计算出来的哈希索引index不会越界;

weak_entry_t结构体

  • weak_entry_t是保存某个对象的所有弱引用(weak)的结构体,目标对象可以被多个弱引用指向,weak_entry_t的其底层结构如下:
struct weak_entry_t {
    DisguisedPtr referent;
    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];
        };
    };

    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)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};
  • DisguisedPtr referent:是被弱引用指向的目标对象;
  • 接下来看到一个联合体,当外界指向目标对象referent的弱引用指针小于等于WEAK_INLINE_COUNT = 4时,使用 inline_referrers定长数组保存这些弱引用指针;
  • 当外界指向目标对象referent的弱引用指针大于4时,使用weak_referrer_t *referrers哈希表存储弱引用指针;
  • bool out_of_line():返回YES时表示使用weak_referrer_t *referrers哈希表存储弱引用指针,返回NO时表示使用 inline_referrers定长数组存储弱引用指针;
weak.png
  • 本文是在objc_818.2源码工程的基础上进行分析的,新建类YYPserson,调试工程如下所示:
Snip20210521_26.png
  • 创建并初始化实例对象person,并使用__weak弱引用指针进行修饰;底层会调用objc_initWeak函数;其实现如下:
id objc_initWeak(id *location, id newObj){
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak
        (location, (objc_object*)newObj);
}
  • 内部调用storeWeak函数,实现如下:
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;

    // 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];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(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.
    //确保目标对象的类class,要被初始化。
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) {
            SideTable::unlockTwo(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.
    //weak指针已指向其他对象,需移除
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    //weak指针指向新的对象,需注册
    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 rejected

        //Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            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);

    // 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;
}
  • 若weak引用指针,之前指向一个旧的对象,现在要指向一个新的对象,那么旧对象的弱引用表要移除weak指针,新对象的弱引用表要新增weak指针;
  • weak_unregister_no_lock(&oldTable->weak_table, oldObj, location):旧对象移除弱引用指针,内部实现如下:
/** 
 * Unregister an already-registered weak reference.
 * This is used when referrer's storage is about to go away, but referent
 * isn't dead yet. (Otherwise, zeroing referrer later would be a
 * bad memory access.)
 * Does nothing if referent/referrer is not a currently active weak reference.
 * Does not zero referrer.
 * 
 * FIXME currently requires old referent value to be passed in (lame)
 * FIXME unregistration should be automatic if referrer is collected
 * 
 * @param weak_table The global weak table.
 * @param referent The object.
 * @param referrer The weak reference.
 */
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结构体
    weak_entry_t *entry;

    if (!referent) return;

    //根据对象在弱引用表中获取weak_entry_t结构体,
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //若存在weak_entry_t结构体,在weak_entry_t中删除弱引用指针
        remove_referrer(entry, referrer);
        //若weak_entry_t结构体中的弱引用指针为空,删除weak_entry_t结构体
        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);
        }
    }
    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}
  • 根据目标对象referent,在弱引用表weak_table_t中获取到weak_entry_t结构体,然后在weak_entry_t结构体中删除弱引用指针referrer,即调用remove_referrer(entry, referrer)函数,实现如下:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    //弱引用指针<=4,存储在inline_referrers数组中
    if (! entry->out_of_line()) {
        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.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }

    //哈希函数计算出弱引用指针在哈希表中的index
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    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.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    //弱引用指针>4,存储在referrers哈希表中
    entry->referrers[index] = nil;
    entry->num_refs--;
}
  • 删除弱引用指针分为两种情况:1.弱引用指针<=4,存储在inline_referrers数组中,2.弱引用指针>4,存储在referrers哈希表中;
  • 在删除完目标对象的弱引用指针之后,会判断weak_entry_t结构体中的弱引用指针数量是否为空,如果为空则删除weak_entry_t结构体,执行weak_entry_remove(weak_table, entry)函数;
  • weak_register_no_lock:新对象注册弱引用指针,实现如下:
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    //目标对象
    objc_object *referent = (objc_object *)referent_id;
    //弱引用指针
    objc_object **referrer = (objc_object **)referrer_id;

    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
            if (deallocatingOptions == 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_entry_t *entry;
    //在weak_table_t中weak_entry_t结构体存在,
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //往weak_entry_t结构体中插入referrer弱引用指针
        append_referrer(entry, referrer);
    } else {
        //在weak_table_t中weak_entry_t结构体不存在,
        //创建一个新的weak_entry_t结构体
        weak_entry_t new_entry(referent, referrer);
        //weak_table_t的扩容
        weak_grow_maybe(weak_table);
        //往weak_table中插入new_entry
        weak_entry_insert(weak_table, &new_entry);
    }
    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.
    return referent_id;
}
  • 首先根据目标对象referent,在弱引用表中weak_table_t,查找weak_entry_t结构体,若weak_entry_t存在,则往weak_entry_t中插入弱引用指针referrer,执行append_referrer函数,weak_entry_t结构体内部插入referrer也分为两种情况,联合体结构,若weak_entry_t不存在,则创建一个新的weak_entry_t结构体,然后weak_table_t扩容,最后插入新建的weak_entry_t结构体;
weak1.png
  • 当YYPerson对象在销毁时,调用dealloc方法,底层会清空弱引用表中的所有弱引用指针,有效的避免了野指针造成的崩溃,dealloc方法的底层实现见 iOS内存管理07 -- retain, release, dealloc与retainCount的源码分析

你可能感兴趣的:(iOS内存管理08 -- weak的底层实现原理)