iOS底层-内存管理之弱引用表

1,997 阅读4分钟

前言

上篇文章讲了散列表中的引用计数表,还有一个弱引用表没有讲。在使用weak进行弱引用时,引用计数表做了什么呢?下面本文将从__weak入手进行分析弱引用表。

Weak原理

  • 先来看看如下代码:

    NSObject *obj = [NSObject alloc];
    NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    __weak typeof(id) wushuang = obj;
    
    NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang);
    NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang);
    NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    

    打印结果如下:

    截屏2021-11-01 11.21.25.png

    对此可能会有疑问,弱引用后到底做了什么?为什么weak指针的引用计数会是2wushuangobj都是指针,为什么obj的引用计数没有变化?带着问题我们去objc4-818.2源码中探索

objc_initWeak

  • 查看汇编可以得知,添加__weak修饰会走到objc_initWeak函数,这个过程是由LLVM来决定的。

    id
    objc_initWeak(id *location, id newObj) // location weak指针地址,newObj object对象
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
          (location, (objc_object*)newObj);
    }
    

    代码中locationweak指针newObjNSObject对象,最终会走到storeWeak,通过观察发现objc_destroyWeak也会走入storeWeak方法,只是传入的参数不同

  • storeWeak主要代码如下

    enum CrashIfDeallocating {
      DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
    };
    template <HaveOld haveOld, HaveNew haveNew,
            enum 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;
     retry:
        if (haveOld) { ... } else { ... }
        if (haveNew) {
            newTable = &SideTables()[newObj]; // 获取对象的散列表
        } else { ... }
    
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // 对散列表进行加锁
    
        if (haveOld  &&  *location != oldObj) { ... }
        // 通过确保没有弱引用对象具有未+初始化的isa,可以防止弱引用机制和+初始化机制之间的死锁
        if (haveNew  &&  newObj) { ... }
        // 销毁时调用方法
        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 ? 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()) { ... }
    
          // Do not set *location anywhere else. That would introduce a race.
          *location = (id)newObj;
      }
      else { }
      
      SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁
      callSetWeaklyReferenced((id)newObj);
      return (id)newObj;
    }
    

    根据代码分析,最终会进入weak_register_no_lock函数

  • 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; // referent:newObject, NSObject 对象
        objc_object **referrer = (objc_object **)referrer_id; // location指针,也就是weak指针
        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(); // 返回false
            }
            else { ... }
            if (deallocating) { ... }
        }
        // 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); // 如果存在则插入
        } 
        else { // 不存在则创建weak_entry_t
            weak_entry_t new_entry(referent, referrer); // 创建weak_entry_t
            weak_grow_maybe(weak_table); // 查看weak_table的size判断是否需要扩容
            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中的weak_entries链表中的weak_entry_t对象entry
      • 如果entry存在,则将weak指针存入weak_entry_t中的referrers链表中,存储时 需要遵循 3/4 两倍扩容规则
      • 如果entry不存在,则根据object对象weak指针创建一个新的entry,再将entry插入weak_tableweak_entries链表中。

根据分析得知__weak并没有引起引用计数的变化,那么也就是说weak对象引用计数变化的因素和打印时调用weak对象有关,通过断点汇编分析,打印时代码会走到objc_loadWeak方法

objc_loadWeak

  • 代码如下:

    id
    objc_loadWeak(id *location)
    {
        if (!*location) return nil;
        return objc_autorelease(objc_loadWeakRetained(location));
    }
    

    代码主要是将objc_loadWeakRetained调用返回的结果,再调用objc_autorelease方法,核心代码就定位到objc_loadWeakRetained

  • objc_loadWeakRetained

    id
    objc_loadWeakRetained(id *location)
    {
        id obj;
        id result;
        Class cls;
        SideTable *table;
     retry:
        // fixme std::atomic this load
        obj = *location; // 根据weak指针获取object对象
        if (obj->isTaggedPointerOrNil()) return obj;
      
        table = &SideTables()[obj]; // 根据object对象获取散列表
        table->lock(); // 加锁 
        if (*location != obj) { ... }
        
        result = obj; 
        cls = obj->ISA();
        if (! cls->hasCustomRR()) {
            ASSERT(cls->isInitialized());
            if (! obj->rootTryRetain()) { // obj引用计数变化
                result = nil;
            }
        }
        else { ... }
        table->unlock();
        return result;
    }
    
    • 根据断点走发现,最终代码进入了rootTryRetain
  • rootTryRetain

    ALWAYS_INLINE bool 
    objc_object::rootTryRetain()
    {
        return rootRetain(true, RRVariant::Fast) ? true : false;
    }
    

    最终走到了我们熟悉的代码rootRetain,原来在此处进行引用计数+1

  • 在使用weak对象时,会短暂的进行引用计数+1,但使用过后会调用objc_release方法进行进行引用计数-1,所以多次调用打印wushuang时,引用计数始终是2

objc_destroyWeak

  • weak对象销毁时会调用objc_destroyWeak,最终代码也会进入storeWeak,通过上面的分析得知,销毁的核心代码是调用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; // object对象
        objc_object **referrer = (objc_object **)referrer_id; // weak指针
        weak_entry_t *entry;
    
        if (!referent) return;
        if ((entry = weak_entry_for_referent(weak_table, referent))) { 
            remove_referrer(entry, referrer); // remove 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); // 移除entry
            }
        }
    }
    

    这部分代码比较好理解,就是先根据object对象和弱引用表获取entry,然后删除weak指针,最后再移除entry

弱引用表结构图

  • 弱引用表结构图如下:

    截屏2021-11-01 16.06.00.png