weak的使用原理

1,272 阅读5分钟

参考: 1、Object Runtime -- Weak 2、iOS 底层解析weak的实现原理 3、Objective-C runtime机制(7)

本文会从以下几个方面讲述weak的使用:

  • 添加弱引用 (一个__weak修饰的变量,底层做了什么)
  • weak有关的数据结构
  • 删除弱引用 (一个对象的引用计数为0时执行dealloc,去除本对象的weak引用)

一、添加弱引用

首先看一个例子:

    NSObject *obj = [[NSObject alloc] init];
    __weak NSObject *weakObj = obj;

__weak NSObject *weakObj = obj,这句代码其实是调用了以下底层代码,具体可看runtime源码,NSObject.mm文件

1、id objc_initWeak(id *location, id newObj)

2、storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> 
(location, (objc_object*)newObj)

3、storeWeak函数内部会调用 weak_register_no_lock()函数,该函数处理弱引用逻辑

1、首先调用 objc_initWeak 函数,函数内部会调用storeWeak函数;
2、在weak_register_no_lock()函数内部会获取弱引用对象所在的SideTable,SideTable中有weak_table_t结构体,weak_table中包含weak_entries数组,内部都是weak_entry_t结构体,每个对象所对应一个entry,entry管理该对象的弱引用关系数组

在看具体的代码之前先看下有关的数据结构:

二、有关的数据结构

1、StripedMap 内部装的SideTable

2、SideTable 封装了weak_table_t

3weak_table_t 全局的weak表 

4weak_entry_t 对象所对应的弱引用

5weak_referrer_t 弱引用

如看下图:

看下具体的数据结构:

1、StripedMap
// 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.
// StripedMap 其实是一个哈希表,
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];
    //hash值生成函数,根据对象的内存地址计算数组中具体的下标值
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }
    ...
 }
 
 2struct SideTable {
    spinlock_t slock;           //自旋锁
    RefcountMap refcnts;        //对象-引用计数hash表 根据对象的指针获得引用计数
    weak_table_t weak_table;    // weak表,内存管理的核心数据结构 类的弱引用关系都放在 weak_entries中

    SideTable() { // 初始化
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() { // 析构函数
        _objc_fatal("Do not delete SideTable.");
    }
    ...
}

3weak_table_t
/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 *  全局的weak表
 */
struct weak_table_t {
    weak_entry_t *weak_entries; // entry数组
    size_t    num_entries;      // 元素个数
    uintptr_t mask;             // 数组的最大下标值,用于辅助计算
    uintptr_t max_hash_displacement; // 用于辅助计算
};

4weak_entry_t
struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 弱引用指向的对象指针
    // 共用体,当弱引用个数大于4时,使用可变数组referrers,当小于等于4时,使用定长数组inline_referrers
    union {
        struct {
            weak_referrer_t *referrers; // 可变数组
            uintptr_t        out_of_line_ness : 2;      // 标记是否越界
            uintptr_t        num_refs : PTR_MINUS_2;    // 弱引用数量
            uintptr_t        mask;                      //最大下标,用于辅助计算弱引用所在index时使用
            uintptr_t        max_hash_displacement;     //用于辅助计算弱引用所在index时使用
        };
        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)); // 内存copy this指向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;
        }
    }
};

添加弱引用的具体实现: runtime源码

// 静态方法
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
    SideTable *newTable;                    // 新SideTable

    // 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) { // 如果有旧值,从SideTbales哈希表中获取旧SideTable,否则赋值nil
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) { // 如果有新值 从SideTbales哈希表中获取新SideTable 否则赋值nil
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 加锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    // 如果有旧值且 弱引用地址和对象地址不相同 姐锁执行retry
    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.
    // 通过确保弱引用对象是否有 -+initialized isa指针,来防止弱引用机制和 +initialized 机制发生死锁
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();// 获取被指向对象的class对象,isa指针为对象的第一个元素,所有isa地址就是class的指针地址
        // 不是已经初始化的类 加锁进行类初始化
        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. 如果类已经完成了initialize初始化
            // If this class is still running +initialize on this thread 如果类还在线程中执行initialize方法
            // (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; // 标记为已经初始化类并 retry

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) { // 如果有旧值,把对象的弱引用指针置为nil并从weak_table表中清掉
        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()) {
            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);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);// 设置弱引用标记位

    return (id)newObj;
}

三、删除弱引用

当一个对象释放的时候,系统调用dealloc方法,其中有一步就是清掉当前对象的weak引用,流程如下:

1dealloc()

2_objc_rootDealloc(self)

3、obj->rootDealloc()

4object_dispose()

5objc_destructInstance()

6objc_clear_deallocating()

7clearDeallocating_slow()

8weak_clear_no_lock(&table.weak_table, (id)this)

四、总结

1、弱引用存放在哪里?

存放在该对象所对应weak_entry_t中的referrers数组中
SideTable -> weak_table_t -> weak_entry_t -> referrers

2、weak修饰属性的流程和__weak修饰变量的流程相同吗

是一样的,只是调用的时机不同,weak修饰属性,是类加载设置成员变量的时候调用objc_storeWeak()函数,这个函数内部也是调用的storeWeak()函数

3、弱引用对象是在什么时候被置为nil的?

系统执行dealloc的时候,内部会处理

涉及到的知识点

1、哈希表
2、TaggedPointer
3、dealloc
4、锁
5、内存管理