iOS weak底层原理及源码解析

面试的时候,经常会问这个,之前面试回答的很简单,就是:底层有个hash表专门来维护存储weak指针,当指向的对象的引用计数为0的时候,就会从这张hash表中删除对应的weak指针,并将weak指针的值置为nil。这样回答也不知道面试官满不满意,虽然没毛病,但是总觉得没有深入回答,如果面试官继续深挖,可能就凉凉了,归其原因还是没有对底层原理及源码进行阅读研究。趁着公司项目不是很紧的时候,抽空阅读了下weak底层的源码,记录如下。

研究方法:

  1. 拥有一份源码,这个可以在苹果开源网站下载 objc4,当然可以打开这个下载的工程进行编译调试,但是我目前用的Xcode11.2.1没办法链接下断点,最终还是走的系统自带的libobjc.dylib动态库,有解决办法的可以帮忙分享下,在此先表示感谢,所以这里也只能另外新建一个工程调试,找到weak实现相关的函数。
  2. 调试方法,首先我们从调用栈看看weak干了什么,找到weak底层的入口函数。
  • 新建一个工程,如下代码:
    NSObject* p = [NSObject new];
    NSObject* p1 = [NSObject new];
    __weak NSObject* weakP = p;
    weakP = p1;
  • 下断点,并打开汇编,如下:
    断点
    打开汇编
  • 运行,结果:
    结果

    我们看到在viewDidLoad汇编代码上,我们看到了一些调用的函数符号,比如objc_msgSend、objc_initWeak、objc_storeWeak、objc_destroyWeak等等,结合写的测试代码看,objc_initWeak对应的应该是我们的__weak NSObject* weakP = p;这句代码,objc_storeWeak对应的应该是这句weakP = p1;当我们的viewDidLoad方法走完,由于我们声明的weakP是一个临时变量,所以还会调用objc_destroyWeak。我们一个一个的分析,这个就要用到源码了,我们在源码工程中全局搜索。

  1. 源码分析
  • objc_initWeak 全局搜索的到了如下结果
    全局搜素objc_initWeak.png

    我们发现有一个.h文件和一个.mm文件,.h这个应该是系统动态库libobjc.dylib的对外的一个接口文件,.mm应该是对应的实现,我们直接到.mm文件,看到如下代码:

/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

注释翻译一下:这个函数是一个新的weak的指针初始化函数,比如

 __weak id weakPtr;
  NSObject *o = ...;
 __weak id weakPtr = o;

而我们的测试代码正好符合这点,说明我们上面的分析应该是没毛病的。再看看其他的,注释中对这个函数的两个参数,第一个就是location指针,对应weak指针,newObj就是指向的对象;函数里面,首先对对象进行的判断是否为空,若为空,直接将weak指针置为空,直接return,若不为空,调用了另外一个函数storeWeak,链接过去看看storeWeak的实现:

 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) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[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.
    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);
        // weak_register_no_lock returns nil if weak store should be rejected

        // 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.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

我们可以看到,这个是一个模板函数,也是weak的核心函数,从这个函数可以看出整个weak的整个流程。函数实现里面一开始就声明了两个数据类型SideTable,我想应该是文章一开始提到的hash表,我们点进去看看:

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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

从源码可以看出,SideTable结构体有三个成员变量:

  • spinlock_t slock;//自旋锁,访问其他两个成员变量的时候加锁
  • RefcountMap refcnts; //引用计数表,内存管理的引用计数
  • weak_table_t weak_table; //弱引用hash表,这个就是目前研究的weak指针存放的地方
    从上面分析可以得出,SideTable是各种弱引用表和引用计数表的拥有者,也可以描述为管理者,而SideTable是存放在哪呢?这个问题后面再研究,先研究下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_table_t结构体有四个成员变量:

  • weak_entry_t *weak_entries;///存储weak指针的动态数组,类型是weak_entry_t
  • size_t num_entries;///已经存放的指针数
  • uintptr_t mask;///参与hash计算的辅助变量
  • uintptr_t max_hash_displacement;///可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
    上面提到weak指针存储在weak_entries的动态数组中,类型是weak_entry_t,那看看weak_entry_t这个类型的定义:
/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
 * is instead a small inline array.
 */
#define WEAK_INLINE_COUNT 4

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> 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;
        }
    }
};

weak_entry_t结构体里面有两个成员变量:
DisguisedPtr<objc_object> referent;//这个是一个将一个对象的指针地址进行特殊处理,就是取负值。另一个是一个union,上面的注释说明了,当存储的weak指针数量小于WEAK_INLINE_COUNT是就用包含静态数组inline_referrers的结构体存储,超过则用另外一个结构体(保含动态数组referrers)存储,这个结构体的成员变量是不是有点眼熟,跟上面的weak_table_t有点类似,所以这个也是一个hash表,他的插入跟查找如下:

  • 插入:
/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {///没有超过out_of_line,使用静态数组存储
        // 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;
            }
        }

        // Couldn't insert inline. Allocate out of line.///超过了out_of_line,使用动态数组存储
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));///new动态数组,大小为WEAK_INLINE_COUNT
        // 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;
    }

    assert(entry->out_of_line());

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {///当已存weak指针数量达到一定数量,动态数组扩容,具体是超过数组容量的3/4的时候扩容,例如存第5个的时候,就会出发动态数组扩容,这个时候是4>4*3/4成立,触发扩容。
        return grow_refs_and_insert(entry, new_referrer);///动态数组扩容
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);///这里参与hash的key是对象的二级指针,就是weak指针所在的地址,也是动态数组中存的元素值,跟weak_table_t的hash算法一样,解决hash冲突方式也是一样,开放地址法
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {///找到为空的位置
        hash_displacement++;
        index = (index+1) & entry->mask;///解决hash冲突
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;///改变最大冲突数
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;///存储weak指针所在的地址值
    entry->num_refs++;///已存空间加1
}

动态数组扩容:

/** 
 * Grow the entry's hash table of referrers. Rehashes each
 * of the referrers.
 * 
 * @param entry Weak pointer hash set for a particular object.
 */
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    assert(entry->out_of_line());

    size_t old_size = TABLE_SIZE(entry);///拿到旧的容量
    size_t new_size = old_size ? old_size * 2 : 8;///默认是扩容到8,其他在原有容量基础上2倍

    size_t num_refs = entry->num_refs;///记录原有已存储的数量
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;///更新mask
    
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));///根据最新容量,new一份新的数组
    entry->num_refs = 0;///初始化为0
    entry->max_hash_displacement = 0;///初始化为0
    
    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            append_referrer(entry, old_refs[i]);///将原有的数组重新经过hash值的运算放入的对应的位置
            num_refs--;
        }
    }
    // Insert
    append_referrer(entry, new_referrer);///将最新的weak指针地址插入动态数组中
    if (old_refs) free(old_refs);///释放掉旧的数组
}
  • 移除对应的weak指针:
/** 
 * Remove old_referrer from set of referrers, if it's present.
 * Does not remove duplicates, because duplicates should not exist. 
 * 
 * @todo this is slow if old_referrer is not present. Is this ever the case? 
 *
 * @param entry The entry holding the referrers.
 * @param old_referrer The referrer to remove. 
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    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;
    }
///超过容量限制,在动态数组中查找删除
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);///已weak指针的地址作为key,hash
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {///hash冲突
        index = (index+1) & entry->mask;///+1往下一个查找
        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;
        }
    }
    entry->referrers[index] = nil;///查找到,置为nil
    entry->num_refs--;///数量-1
}

好了,到目前为止,我们把数据结构基本上理了一遍,有了基本的印象,我们回到storeWeak函数。
storeWeak这个是一个template函数,我们先看看template的参数:

  • HaveOld haveOld, ///标记weak指针有指向的值
  • HaveNew haveNew,///标记weak指针需要指向新值
  • CrashIfDeallocating crashIfDeallocatin///标记当引用的对象dealloc的时候,weak指针不需要保存。
    继续看,一开始声明的两个SideTable类型的表进行了判断赋值:
 if (haveOld) {///有旧值
        oldObj = *location;///拿到旧对象,作为后面从旧hash表中移除weak指针的key值
        oldTable = &SideTables()[oldObj];///拿到旧的hash表
    } else {
        oldTable = nil;///没有旧值,旧hash表赋值为nil
    if (haveNew) {///有新值
        newTable = &SideTables()[newObj];///拿到新的hash表
    } else {
        newTable = nil;///没有新值,新hash表赋值为nil
    }

从上面的代码可以看到有个关键的函数SideTables(),可以进去看看:

// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global 
// pointer to this struct because of the extra indirection.
// Do it the hard way.
alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];///全局数组

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);///将全局数组强转为StripedMap<SideTable>*>类型
}

是一个全局函数,返回的是一个StripedMap<SideTable>类型的引用,从字面上理解,应该是一个value为SideTable类型的map,我们知道NSDictionary的底层就是hashMap实现的,我们看看StripedMap<SideTable>是不是:

enum { CacheLineSize = 64 };

// StripedMap<T> is a map of void* -> T, sized appropriately 
// for cache-friendly lock striping. 
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
template<typename T>
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<uintptr_t>(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<StripedMap<T>>(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
};

StripedMap<T>是一个模板类,成员变量是一个数组,对iPhone而言是数组的count大小为64,数组元素的大小都是64位对齐,是一个void* -> T的map。那么,SideTables()全局函数,返回的就是一个静态全局是一个objc_object* -> SideTable的hashmap。
StripedMap类里面还定义了相关index的hash算法:

static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;///确保不超过StripeCount,防止越界,当然就有可能,多个obj共用一个sideTable
    }

重新定义了[]:

 T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }

其他的是锁相关的操作函数,对访问sideTable的加锁,同一时间只能一个资源能访问同一个sideTable。
上面提到过,SideTables()的到的是一个大小固定为64的数组,那么肯定会出现,多个obj共用一个sideTable,具体到weakTable也会多个Obj就会共用一个,到了weakTable里面还会对objc的地址在次进行hash运算,具体到函数如下:

  • 查找weak_entry_t
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;///hash算法
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {///找到对应对象的存放位置,需要处理hash冲突,如果存在hash冲突具体会往下一个查找,直到找到空位,这个就是开放地址法处理hash冲突
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;///冲突处理次数加1
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;///当超过了最大的冲突处理次数后,说明没有查找到,就会返回nil。
        }
    }
    
    return &weak_table->weak_entries[index];///查找到返回对应weak_entry_t
}
  • 而插入新的weak_entry_t就是如下的逻辑:
/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 */
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);

    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);///跟查找一样的hash算法
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {///找到为nil的位置
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;///当前的最大冲突次数
    }

    weak_entries[index] = *new_entry;///存储weak_entry_t
    weak_table->num_entries++;///已存储位置数+1

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;///与max_hash_displacement当前冲突次数大,则赋值给max_hash_displacement
    }
}
  • 当然,插入新的weak_entry_t的时候,会先判断weak_table_t是否需要扩容:
// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {///超过最大容量的3/4
        weak_resize(weak_table, old_size ? old_size*2 : 64);///在原来的容量基础上*2,默认初始值是64
    }
}

  • 移除weak_entry_t:
/** 
 * Remove old_referrer from set of referrers, if it's present.
 * Does not remove duplicates, because duplicates should not exist. 
 * 
 * @todo this is slow if old_referrer is not present. Is this ever the case? 
 *
 * @param entry The entry holding the referrers.
 * @param old_referrer The referrer to remove. 
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    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;
    }
///从动态数组中移除
    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;
        }
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}

好了到目前为止,我们可以清晰的看到weak存储的整个数据结构及关系,如下:
weak的存储的数据结构及关系

再次回到storeWeak函数:

// Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);///如果有旧指向,从旧表中移除weak地址值
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);///在新表中插入weak指针的地址,成功返回newObj,否则返回nil
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {///Tagged Pointer是苹果在64位系统之后,用来优化内存的,下一个内存管理篇研究下
            newObj->setWeaklyReferenced_nolock();///在引用计数表(下一个研究对象)中标记有弱引用指向,当引用计数为0时,触发移除相对应的weak指针。
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;///将weak指针指向新的对象
    }
    else {
        // No new value. The storage is not changed.
    }

这里需要研究两个函数:

  • weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
    crashIfDeallocating);///插入weak指针
/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.///是否存在弱引用weak_entry_t,有,则在对应的数组中插入当前需要插入的weak指针,没有,新建一个weak_entry_t数据插入到弱引用表中
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
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;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;///运用TaggedPointer计数,无需维护weak_table_t弱引用表

    // ensure that the referenced object is viable
    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);
    }

    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_entry_t *entry;///弱引用
    if ((entry = weak_entry_for_referent(weak_table, referent))) {///在弱引用表中查找weak_entry_t,存在
        append_referrer(entry, referrer);///插入weak指针,上面分析过
    } 
    else {///没有weak_entry_t
        weak_entry_t new_entry(referent, referrer);///new一份weak_entry_t
        weak_grow_maybe(weak_table);///判断weak_table是否需要扩容,上面提到过
        weak_entry_insert(weak_table, &new_entry);///插入weak指针,上面提到过
    }

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

    return referent_id;
}
  • weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);///移除weak指针
/** 
 * 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 *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {///查找weak_entry_t
        remove_referrer(entry, referrer);///移除对应的weak指针
        bool empty = true;///对应的weak_entry_t是否移除空了
        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);///如果entry里面的数组被移除空了,就要从weak_table表中将对应的entry移除
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}
  • 这里需要看下从weak_table表中将对应的entry移除:
/**
 * Remove entry from the zone's table of weak references.
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line()) free(entry->referrers);///如果是静态数组,直接释放静态数组
    bzero(entry, sizeof(*entry));///释放entry

    weak_table->num_entries--;///对应的已存数量-1

    weak_compact_maybe(weak_table);///这里会查看下weakTable的容量是否需要减容,上面讲到过扩容
}
  • weak_compact_maybe
// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {当就容量超过1024且,是利用率不及1/16的时候,减容
        weak_resize(weak_table, old_size / 8);///在原有的基础上除以8
        // leaves new table no more than 1/2 full
    }
}

总结下:

  • 底层数据结构
  1. weak的底层的实现的数据结构是嵌套的hash表,首先是在全局的散列表SideTables中(hashMap)管理了固定的64份sideTble,key是object,这样就会出现多个object对应同一个sideTble,SideTables解决hash冲突是类似拉链方式,每个sideTble对应着一张弱引用表weak_table,这里的弱引用类似于一个链表,实际是动态数组方式;
    2、在weak_table中通过对object的地址再次hash找到真正的所在动态数组的位置,然后每个位置存放的是weak_entry_t,在插入新的weak_entry_t的时候可能会扩容,反之,当weak_entry_t的nums为空的时候,需要移除weak_entry_t,这个时候可能会减容;
    3、这里的weak_entry_t也是一张hash表,是weak指针的地址作为key,hash后,计算到对应数组的下标index,将weak指针的地址保存起来,当数据小于4个的时候,使用的是静态数组,超过这个数的时候,会使用动态数组,跟上面的weak_table一样,也是会涉及到扩容,默认是8,其他情况是*2。
  • 流程:
    1、初始化一个weak指针并赋值时,会调用id
    objc_initWeak(id *location, id newObj);
    2、将weak指针指向另外一个对象时,会调用id
    objc_storeWeak(id *location, id newObj);
    3、释放一个weak指针时,会调用void
    objc_destroyWeak(id *location);
    上面三个都会调用template <HaveOld haveOld, HaveNew haveNew,
    CrashIfDeallocating crashIfDeallocating>
    static id
    storeWeak(id *location, objc_object *newObj);这个模板函数,只不过传的参数有区别,初始化,没有旧值需要处理,重新指向跟释放,有旧值需要处理,后面两个的区别是,释放没有新值需要处理。这些具体到函数是:
    a. 移除旧值void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
    id *referrer_id)
    b. 插入新值id
    weak_register_no_lock(weak_table_t *weak_table, id referent_id,
    id *referrer_id, bool crashIfDeallocating)
    以上,欢迎指正。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容