美文网首页源码解析
iOS weak 的实现

iOS weak 的实现

作者: 哦呵呵y | 来源:发表于2018-10-15 14:56 被阅读117次

    iOS ARC中引用计数的实现
    iOS weak 的实现
    ARC中的数据结构以及寻址方式

    SideTables 是一个全局的 hash 表,用来存储对象多余的引用计数,以及弱引用表

    一、 SideTables

    image.png
    1. SideTables 是一个全局的 hash 表, 其中包含了一个数量为64的数组,数组中的存储的为SideTable结构体,其中通过对象内存地址作为hash表的key
        enum { StripeCount = 64 };
    
        PaddedT array[StripeCount];
    
        static unsigned int indexForPointer(const void *p) {
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
        }
    
    
    1. SideTable中包含一个 c++ Map RefcountMap refcnts 用来对象存储额外的引用计数,一个结构体weak_table_t weak_table 用来存储对象的弱引用数据

    image.png

    RefcountMap refcnts 中通过一个size_t(64位系统中占用64位)来保存引用计数,其中1位用来存储固定标志位,在溢出的时候使用,一位表示正在释放中,一位表示是否有弱引用,其余位表示实际的引用计数

    1. RefcountMap refcnts 是一个C++的对象,内部包含了一个迭代器
    2. 其中以DisguisedPtr<objc_object> 对象指针为key,size_t 为value保存对象引用计数
    3. 将key、value通过std::pair打包以后,放入迭代器中,所以取出值之后,.first代表key,.second代表value
      在上一篇中、retain release方法会使用到SideTables来存放引用计数
    // Move some retain counts to the side table from the isa field.
    // Returns true if the object is now pinned.
    bool 
    objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
    {
        assert(isa.nonpointer);
        SideTable& table = SideTables()[this];
    
        size_t& refcntStorage = table.refcnts[this];
        size_t oldRefcnt = refcntStorage;
        // isa-side bits should not be set here
        assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
        assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    
        if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
    
        uintptr_t carry;
        size_t newRefcnt = 
            addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
        if (carry) {
            refcntStorage =
                SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
            return true;
        }
        else {
            refcntStorage = newRefcnt;
            return false;
        }
    }
    
    
    // Move some retain counts from the side table to the isa field.
    // Returns the actual count subtracted, which may be less than the request.
    size_t 
    objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
    {
        assert(isa.nonpointer);
        SideTable& table = SideTables()[this];
    
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()  ||  it->second == 0) {
            // Side table retain count is zero. Can't borrow.
            return 0;
        }
        size_t oldRefcnt = it->second;
    
        // isa-side bits should not be set here
        assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
        assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    
        size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
        assert(oldRefcnt > newRefcnt);  // shouldn't underflow
        it->second = newRefcnt;
        return delta_rc;
    }
    
    1. 第一个方法用来将多余的引用计数保存到SideTables中, 第二个方法用来取出引用计数
    2. 其中都用到了SideTables中的RefcountMap refcnts,但是两个方法的实现并不相同
    // 第一种方式
        SideTable& table = SideTables()[this];
    
        size_t& refcntStorage = table.refcnts[this];
        size_t oldRefcnt = refcntStorage;
    
    
     // 第二种方式
        SideTable& table = SideTables()[this];
    
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()  ||  it->second == 0) {
            // Side table retain count is zero. Can't borrow.
            return 0;
        }
        size_t oldRefcnt = it->second;
    
    1. 其实第一种方式是通过重载[]操作符来实现查找的,但是添加的时候需要判断如果不存在记录的话,需要新增一条数据保存,所以使用了两种不同的方法
     // 通过操作符重载实现 查找、新增插入
      value_type& FindAndConstruct(const KeyT &Key) {
        BucketT *TheBucket;
        if (LookupBucketFor(Key, TheBucket))
          return *TheBucket;
    
        return *InsertIntoBucket(Key, ValueT(), TheBucket);
      }
    
      ValueT &operator[](const KeyT &Key) {
        return FindAndConstruct(Key).second;
      }
    
    1. 两个方法都是获取到RefcountMap refcnts 进行引用计数的增加和减少

    weak_table_t 弱引用表
    1. 全局弱引用表 weak_table_t
    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    
    1. 弱引用表的内部结构
    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];
            };
        };
    };
    

    1️⃣. 其中主要包含了两个属性DisguisedPtr<objc_object> referent 对象指针,还有一个容器类保存所以只需这个对象的弱引用
    2️⃣. 共用体中包含两种结构体,当弱引用数量少于4的时候,使用数据结构来存储,当超过4个的时候使用hash表进行存储,out_of_line_ness 默认为 ob00,当弱引用数量大于4的时候,设置为 REFERRERS_OUT_OF_LINE ob10,通过判断out_of_line_ness来决定用什么方式存储
    3️⃣. weak_referrer_t *referrers 是一个二级指针实现的hash表


    二、 weak 的实现原理

    {
      id obj1 = [[NSObject alloc] init];
      id __weak obj2 = obj1;
    }
    

    当我们创建弱引用对象时,系统会编译成以下代码

    id obj2;
    objc_initWeak(&obj2, obj1);
    objc_destroyWeak(&obj2);
    

    通过 objc_initWeak(&obj2, obj1);创建weak引用,在对象作用域结束时,使用objc_destroyWeak(&obj2);来释放引用

    1. 创建弱引用
    id
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
            (location, (objc_object*)newObj);
    }
    
    void
    objc_destroyWeak(id *location)
    {
        (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
            (location, nil);
    }
    

    两个方法最终都调用了storeWeak,但是两个方法调用参数不同。
    init方法中,将要引用的对象作为参数传递,并且HaveOld参数为false,HaveNew参数为true
    而destroy方法中,将nil作为新对象传入,并且HaveOld参数为true,HaveNew参数为false

    HaveOld 代表是否有旧的引用,如果为true,则代表有旧的引用需要释放
    HaveNew 代表是否有新的引用,如果为true,则代表要存储新的引用
    
    template <bool HaveOld, bool HaveNew, bool 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.
    // 如果有新值,判断新值所属的类是否已经初始化,如果没有初始化,则先执行初始化,防止+initialize内部调用storeWeak产生死锁
        if (HaveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                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;
    
                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.
    // 如果存储成功则设置SideTable中弱引用标志位
            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;
    }
    

    以上就是 store_weak 这个函数的实现,它主要做了以下几件事:

    1. 分别获取新旧值的散列表指针
    2. 如果有旧值就调用 weak_unregister_no_lock 函数清除旧值
    3. 如果有新值就调用 weak_register_no_lock 函数分配新值

    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;
    
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // 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))) {
            append_referrer(entry, referrer);
        } 
        else {
            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;
    }
    

    weak_register_no_lock 用来保存弱引用信息,具体实现如下:

    1. 判断对象是否可以弱引用,是否正在释放。
    2. 查询weak_table_t, 判断对象是否已经保存有相关联的弱引用信息
    3. 如果已经有相关弱引用信息,则调用append_referrer方法添加进现在的weak_entry_t结构中,如果没有相关联信息,则创建weak_entry_t 节点,并且插入到weak_table_t弱引用表中。

    weak_unregister_no_lock 实现:

    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))) {
            remove_referrer(entry, referrer);
            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.
    }
    

    weak_unregister_no_lock 用来移除弱引用信息,具体实现如下:

    1. 查询弱引用表中是否保存有相关联的弱引用信息
    2. 如果有,则调用remove_referrer方法移除相关联的弱引用信息
    3. 移除相关联弱引用信息之后,判断存储数组是否为空,如果为空,则调用weak_entry_remove移除weak_entry_t节点

    在dealloc中,释放对象时会调用一下方法

    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);
        }
        table.unlock();
    }
    

    此方法用来移除SideTables中对象相关联的数据

    1. 判断是否有弱引用,如果有调用weak_clear_no_lock方法移除所有相关联弱引用
    2. 判断是否有额外的引用计数存储在SideTables中,如果有则调用table.refcnts.erase(this);清除
    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);
        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;
        
        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];
            if (referrer) {
                if (*referrer == referent) {
                    *referrer = nil;
                }
                else if (*referrer) {
                    _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);
    }
    

    weak_clear_no_lock 方法用来清空对象相关联的所有弱引用数据,具体实现如下:

    1. 判断对象是否有相关联的弱引用数据,如果没有则直接return
    2. 通过out_of_line判断实际存储结构,如果为0b10则用的是指针数组,否则是长度为4的数组结构
    3. 遍历数组,将所有对象设置为nil
    4. 调用weak_entry_remove(weak_table, entry);移除节点

    相关文章

      网友评论

        本文标题:iOS weak 的实现

        本文链接:https://www.haomeiwen.com/subject/opzvgftx.html