美文网首页
iOS底层-- weak修饰对象存储原理

iOS底层-- weak修饰对象存储原理

作者: Engandend | 来源:发表于2020-05-19 23:48 被阅读0次

    问题:为何weak修饰的变量可以打破循环引用?
    因为weak修饰的变量存储在散列表中的弱引用表里,不参与引用计数器的使用,也就是说,在进行释放额时候,不管你怎么引用,直接就把你置空了。

    基本概念

    • SideTable :散列表:系统自动维护,用于存储/管理一些信息
      SideTable的结构中能看到 其管理三种表:
      spinlock_t slock 自旋锁表;
      RefcountMap refcnts 引用计数表;
      weak_table_t weak_table 弱引用表;
    • weak_table:弱引用对象存储的表,是SideTable中的一张表
    • weak_entry_t:weak_table里面的一个单元,用于管理当前类的弱引用对象,可以理解为一个数组,查看weak_entry_t的结构,有助于更好的理解里面的存储结构,里面包含一个weak_referrer_t,相当于一个数组,这里面的就是存储的弱引用对象,还有其他的一些信息,比如mask(蒙版,容量-1)、num_refs (当前存储的数量)等
    • weak_referrer_t :weak_entry_t 中的弱引用对象,相当于是 数组中的一个元素

    存储原理

    1、源码探索入口

    写上这样的代码,打上断点,并打开汇编模式:debug->debug workflow -> alway show disassembly

    - (instancetype)init
    {
        if (self = [super init]) {        
    ️        id __weak weakSelf = self;    //断点在这
        }
        return self;
    }
    

    运行后会进入断点,出现这样的一些信息


    汇编模式信息

    找到callq方法:objc_initWeak ,拿到这个方法就可以进入源码去调试了

    源码探索

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

    1.1、内部做的操作是 存储weak—storeWeak

    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        ......
    
        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();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                class_initialize(cls, (id)newObj);
                goto retry;
            }
        }
    
    // 有旧值,进行 weak_unregister_no_lock操作
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
    // 有新值  进行weak_register_no_lock 操作
        if (haveNew) {
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
    
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
            *location = (id)newObj;
        }
        else {
        }
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj;
    }
    

    关键步骤:
    1、如果cls还没有 初始化,先初始化这个类

    2、如果weak对象有旧值,先对旧值 进行 weak_unregister_no_lock,删除旧值
    3、如果weak对象有新值 就对新值进行weak_register_no_lock,新增新值

    1.2、再来看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;
    // 在 weak_table 中去找到 有 referent 的 entry (相当于在weak_table 表中去找到包含referent 元素的 数组)
       if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
    // 找到了 这个  entry,  就删除 entry 中的  引用对象-referrer
            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;
                    }
                }
            }
    
    // 如果 entry 中的引用对象 没有了 删除这个 entry
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    }
    

    关键步骤
    1、在 weak_table 中去找到 有 referent-引用对象的 entry (相当于在weak_table 表中去找到包含referent 元素的 数组)
    2、如果找到了 entry 就删除 entry中的 referent-引用对象
    3、判断 entry 里面 还有没有其他对象,如果没有,就把entry也remove掉(相当于数组中的元素为空,就把这个数据也删掉)

    1.3、 存储新值:weak_register_no_lock

    id 
    weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                          id *referrer_id, bool crashIfDeallocating)
    {
        ......
    
        weak_entry_t *entry;
    // 在 weak_table 中去找到 有 referent 的 entry (相当于在weak_table 表中去找到包含referent 元素的 数组)
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
    // 如果找到,直接append
            append_referrer(entry, referrer);
        } 
        else {
    // 如果没有找到相应的 entry ,就创建一个entry 并插入 weak_table
            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;
    }
    

    关键步骤
    1、在 weak_table 中去找 有 referent 的 entry (相当于在weak_table 表中去找到包含referent 元素的 数组)
    2、如果找到entry,进行添加操作:append_referrer
    - 2.1、如果有空位,直接插进去
    ---- 这里有一个疑问:为什么会有一个空位?这里可以看new_entry的实现:初始容量为4,并默认4个空值
    - 2.2、如果数量超过容量的3/4,进行扩容,再添加(这里想到,方法缓存机制,方法缓存也是超过3/4进行扩容,方法的扩容是:扩容之后,以前的方法删掉了,再把需要缓存的方法插进去)
    3、如果没找到entry,创建一个entry,在进行插入


    大概的过程是这样的:


    大概流程

    释放原理

    弱引用对象在释放的时候,可以在dealloc中去看具体是怎么释放的
    dealloc -》
    rootDealloc -》
    object_dispose -》
    objc_destructInstance -》
    clearDeallocating -》
    clearDeallocating_slow

    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            bool assoc = obj->hasAssociatedObjects();
    // 如果有关联对象,就remove掉 
            if (assoc) _object_remove_assocations(obj);
    
    // 弱引用的释放在这里
            obj->clearDeallocating();
        }
    
        return obj;
    }
    
    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)) {
      //  在这里进行释放
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    
    
    // 找到散列表中的 weak_table 表,找到weak_table 中的 entry,将entry中的 引用对象referrer 置空,最后remove entry
    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();
    }
    

    总之,释放的时候就是找到散列表中的 weak_table 表,找到weak_table 中的 entry,将entry中的 引用对象referrer 置空,最后remove entry

    最后补充一张存储结构图 来源

    存储结构图

    补充

    • 1、strong
      同样的方法,进入strong底层,可以看到,底层默认实现的是 retain 方法,所以在arc中,strong其实等同于 retrain

    • 2、weak与assign
      assign一般只修饰值类型,虽然也可以修饰引用类型,但是修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
      weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。

    相关文章

      网友评论

          本文标题:iOS底层-- weak修饰对象存储原理

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