美文网首页
Weak详解

Weak详解

作者: Joolybgo | 来源:发表于2018-10-15 12:20 被阅读33次

    weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起崩溃的情况,它主要用于解决循环引用。

    NSObject *obj = [[NSObject alloc] init];
    __weak id obj1 = obj;
    obj1 = @"123";
    
    ...
    0000000100000efd         call       imp___stubs__objc_initWeak
    0000000100000f02         lea        rdi, qword [rbp+var_20]                     ; argument "addr" for method imp___stubs__objc_storeWeak
    0000000100000f06         lea        rsi, qword [cfstring_123]                   ; @"123", argument "value" for method imp___stubs__objc_storeWeak
    0000000100000f0d         mov        qword [rbp+var_28], rax
    0000000100000f11         call       imp___stubs__objc_storeWeak
    0000000100000f16         lea        rdi, qword [rbp+var_20]                     ; argument "instance" for method imp___stubs__objc_destroyWeak
    0000000100000f1a         mov        dword [rbp+var_4], 0x0
    0000000100000f21         mov        qword [rbp+var_30], rax
    0000000100000f25         call       imp___stubs__objc_destroyWeak
    ...
    

    从上面代码可以看出创建weak是通过objc_initWeak方法,赋值是通过objc_storeWeak,跟属性用weak关键字调用的同一个方法,销毁是通过objc_destroyWeak方法。

    id objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    id objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    

    从上面可以看出weak的核心都是调用storeWeak方法,区别是模板的几个参数,创建是没有旧值得,之后赋值都是有旧值得。

    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    static id  storeWeak(id *location, objc_object *newObj)
    {
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
     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;
        }
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            // 判断 isa 非空且已经初始化
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
                previouslyInitializedClass = cls;
                goto retry;
            }
        }
        
        if (haveOld) {//清理旧值
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
        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;
    }
    

    这个函数首先引入了第一个结构SideTable,还可以通过SideTables得到这个结构的对象,看下他们的结构。

    alignas(StripedMap<SideTable>) static uint8_t 
        SideTableBuf[sizeof(StripedMap<SideTable>)];
    static void SideTableInit() {
        new (SideTableBuf) StripedMap<SideTable>();
    }
    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    

    SideTableInit这个方法在程序开始运行的时候都调用了,它初始化了StripedMap,而且SideTables()获取到就是这个,每次获取都是最新值。

    template<typename T>
    class StripedMap {
        enum { CacheLineSize = 64 };
    #if TARGET_OS_EMBEDDED
        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]; 
        }
        ....
    

    StripedMap结构就有一个成员,PaddedT array[StripeCount],从这可以看出这个数组有64个PaddedT结构的变量,每个PaddedT结构占64个字节,也就是说StripedMap结构大小就是4096个字节。PaddedT在这里就是SideTable。通过indexForPointer方法可以看到,分配SideTable的时候,是通过对象的地址的((addr >> 4) ^ (addr >> 9)) % StripeCount运算得到的,因为最后是做64的模运算,所以结果只能是从0-63。

    using spinlock_t = mutex_tt<LOCKDEBUG>;
    template <bool Debug>
    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
        ...
    struct SideTable {
        spinlock_t slock;//本质就是os_unfair_lock锁
        RefcountMap refcnts;//weak这里暂时用不上 arc会用到
        weak_table_t weak_table;//weak表
    
        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);
    };
    
    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    

    StripedMap初始化成员array的时候,就初始化了64个SideTable,每个SideTable初始化都会把weak_table空间置为0。从上面可以看出来每一个弱引用都是一个weak_entry_t对象。举个很形象的例子,有一个宿舍楼(SideTables),他有64个宿舍(SideTable),宿舍有很多个床位(weak_entry_t),它会根据里面人的多少来进行适当地扩容,而且每个宿舍都有一把锁(spinlock_t)来保护财产安全,当一个进去时就锁上,出来时解锁。至于怎么分配宿舍,是根据人的编号来行进的,在这里就是根据对象的地址,再根据上面的indexForPointer方法来进行分配。

    #define WEAK_INLINE_COUNT 4
    #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;
            }
        }
    };
    

    经过后面的一些代码的验证,可以看出referent它存放的是weak要引用的对象,而inline_referrers存放的是weak对象本身,初始化为4个,如果有多于4的时候,会进行扩容,也就是有多个weak指向同一个对象。至于DisguisedPtr<objc_object>,你可以把它当成id。有了这些,看后面的代码会清晰很多。

    现在回过头看storeWeak函数,haveOld为真的时候,大部分情况是weak指针指向新值了,还有一种情况是weak属性的时候。weak_unregister_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;
    
        // 保证引用对象是否有效
        bool deallocating;
        // 是否有自定义的默认方法,如retain/release
        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;
        //获取weak引用对象
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            //如果存在就把weak对象 加进去
            append_referrer(entry, referrer);
        } 
        else {
            //创建weak弱引用表关联
            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弱引用:

    weak_entry_t new_entry(referent, referrer);
    //对应于
    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;
            }
        }
    

    从这就能看出referent对应的是weak要引用的对象,inline_referrers对应的是weak本身。

    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) {
            weak_resize(weak_table, old_size ? old_size*2 : 64);
        }
    }
    
    static void weak_resize(weak_table_t *weak_table, size_t new_size)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        weak_entry_t *old_entries = weak_table->weak_entries;
        weak_entry_t *new_entries = (weak_entry_t *)
            calloc(new_size, sizeof(weak_entry_t));
    
        weak_table->mask = new_size - 1;
        weak_table->weak_entries = new_entries;
        weak_table->max_hash_displacement = 0;
        weak_table->num_entries = 0;  // restored by weak_entry_insert below
        
        if (old_entries) {
            weak_entry_t *entry;
            weak_entry_t *end = old_entries + old_size;
            for (entry = old_entries; entry < end; entry++) {
                if (entry->referent) {
                    weak_entry_insert(weak_table, entry);
                }
            }
            free(old_entries);
        }
    }
    

    从一开始old_size ? old_size*2 : 64为0,所以开辟出了64个大小为sizeof(weak_entry_t)空间,如果里面的个数超过64的3/4了,就开始再次扩容,并把之前的表结构再一个一个插入进去,weak_entry_insert(weak_table, entry);

    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);
        size_t index = begin;
        size_t hash_displacement = 0;
        while (weak_entries[index].referent != nil) {
            index = (index+1) & weak_table->mask;
            if (index == begin) bad_weak_table(weak_entries);
            hash_displacement++;
        }
    
        weak_entries[index] = *new_entry;
        weak_table->num_entries++;
    
        if (hash_displacement > weak_table->max_hash_displacement) {
            weak_table->max_hash_displacement = hash_displacement;
        }
    }
    

    hash_pointer是根据对象的地址做一次hash运算,用的是ptr_hash方法,再跟总共的空间做与运行,也就是跟63做与运算(如果再次扩容就不是63了)。如果当前位置有值了,就再通过上面算法再换一个,接下来就是简单赋值了。

    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;
        size_t index = begin;
        size_t hash_displacement = 0;
        while (weak_table->weak_entries[index].referent != referent) {
            index = (index+1) & weak_table->mask;
            if (index == begin) bad_weak_table(weak_table->weak_entries);
            hash_displacement++;
            if (hash_displacement > weak_table->max_hash_displacement) {
                return nil;
            }
        }
        
        return &weak_table->weak_entries[index];
    }
    

    通过一个对象获取对应的weak_entry_t,这里跟插入的差不多,都是根据地址来进行操作。

    static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
    {
        if (! entry->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.
            weak_referrer_t *new_referrers = (weak_referrer_t *)
                calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
            // 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) {
            return grow_refs_and_insert(entry, new_referrer);
        }
        size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        while (entry->referrers[index] != nil) {
            hash_displacement++;
            index = (index+1) & entry->mask;
            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;
        entry->num_refs++;
    }
    

    从上面可知,有2种模式,如果有小于等于4个weak的话,就用inline_referrers本身,就如最开始for循环,如果大于4个的话,就跟weak表结构一样了,用referrers进行存储了,如果里面个数大于3/4就自动扩容一倍,它用的是w_hash_pointer方法对地址进行hash取值,其实跟hash_pointer一样,调用的都是同一个方法ptr_hash

    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);
            }
        }
    }
    
    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;
                }
            }
            ...
        }
    
        size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        ....
        entry->referrers[index] = nil;
        entry->num_refs--;
    }
    

    移除weak引用,主要就是把相应位置的指针置为空,entry->inline_referrers[i] = nil;entry->referrers[index] = nil;。如果没有引用了,就直接调用weak_entry_remove方法把weak表里面的相应引用给置为空。

    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));
        weak_table->num_entries--;
        weak_compact_maybe(weak_table);
    }
    

    当一个对象销毁的时候,如果有弱引用,会调用weak_clear_no_lock方法进行清除,最后也会调用weak_entry_remove

    inline void  objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {
            // Slow path for raw pointer isa.
            sidetable_clearDeallocating();
        }else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            // Slow path for non-pointer isa with weak refs and/or side table data.
            clearDeallocating_slow();
        }
    }
    
    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();
    }
    
    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) return;
        
        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;
                }
                ...
            }
        }
        weak_entry_remove(weak_table, entry);
    }
    

    这里的逻辑跟之前相似,看上面的即可。weak的介绍到此为止,详细的可以自己运行别人编译好的runtime自己进行调式。

    结构图

    相关文章

      网友评论

          本文标题:Weak详解

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