美文网首页面经
weak指针实现的原理

weak指针实现的原理

作者: 昵称是乱起的 | 来源:发表于2019-01-25 22:17 被阅读20次
        NSObject *obj = [[NSObject alloc] init];
        id __weak obj1 = obj;
    

    当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数,objc_initWeak(&obj1,obj)

    #objc_initWeak(&obj1,obj) 
    id
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {//newObj为空直接把指针释放掉
            *location = nil;
            return nil;
        }
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    

    这里判断了其指针指向的类对象是否有效,无效直接释放,object将被注册为一个指向value的__weak对象,storeWeak<false/*DontHaveOld*/, true/*DoHaveNew*/, true/*DoCrashIfDeallocating*/>,传递了3个参数过去
    (location, (objc_object*)newObj);

    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    // HaveOld:  true - 变量有值
    //          false - 需要被及时清理,当前值可能为 nil
    // HaveNew:  true - 需要被分配的新值,当前值可能为 nil
    //          false - 不需要分配新值
    // CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
    //          false - 用 nil 替代存储
    template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
    static id storeWeak(id *location, objc_object *newObj) {
        // 该过程用来更新弱引用指针的指向
        // 初始化 previouslyInitializedClass 指针
        Class previouslyInitializedClass = nil;
        id oldObj;
        // 声明两个 SideTable
        // ① 新旧散列创建
        SideTable *oldTable;
        SideTable *newTable;
        // 获得新值和旧值的锁存位置(用地址作为唯一标示)
        // 通过地址来建立索引标志,防止桶重复
        // 下面指向的操作会改变旧值
    retry:
        if (HaveOld) {//传进来的是false,这里面不会进去
            // 更改指针,获得以 oldObj 为索引所存储的值地址
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        if (HaveNew) {
            // 更改新值指针,获得以newObj为索引做hash运算得到的SideTable
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
        // 加锁操作,防止多线程中竞争冲突
        SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
        // 避免线程冲突重处理
        // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
        if (HaveOld  &&  *location != oldObj) {
            SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
            goto retry;
        }
        // 防止弱引用间死锁
        // 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
        if (HaveNew  &&  newObj) {
            // 获得新对象的 isa 指针
            Class cls = newObj->getIsa();
            // 判断 isa 非空且已经初始化
            if (cls != previouslyInitializedClass  &&
                !((objc_class *)cls)->isInitialized()) {
                // 解锁
                SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
                // 对其 isa 指针进行初始化
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
                // 如果该类已经完成执行 +initialize 方法是最理想情况
                // 如果该类 +initialize 在线程中
                // 例如 +initialize 正在调用 storeWeak 方法
                // 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
                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 方法返回 nil
            // 在引用计数表中设置若引用标记位
            if (newObj  &&  !newObj->isTaggedPointer()) {
                // 弱引用位初始化操作
                // 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
                newObj->setWeaklyReferenced_nolock();
            }
            // 之前不要设置 location 对象,这里需要更改指针指向
            *location = (id)newObj;
        }
        else {
            // 没有新值,则无需更改
        }
        SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
        return (id)newObj;
    }
    
    struct SideTable {
        // 保证原子操作的自旋锁
        spinlock_t slock;
        // 引用计数的 hash 表
        RefcountMap refcnts;
        // weak 引用全局 hash 表
        weak_table_t weak_table;
    };
    struct weak_table_t {
        // 保存了所有指向指定对象的 weak 指针
        weak_entry_t *weak_entries;
        // 存储空间
        size_t    num_entries;
        // 参与判断引用计数辅助量
        uintptr_t mask;
        // hash key 最大偏移值
        uintptr_t max_hash_displacement;
    };
    

    这是一个全局弱引用hash表。使用不定类型对象的地址作为 key ,用 weak_entry_t 类型结构体对象作为 value 。其中的 weak_entries 成员,从字面意思上看,即为弱引用表入口。其实现也是这样的。
    其中weak_entry_t是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用hash表。其定义如下:

    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];
            };
        };
    };
    
    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_entry_for_referent

    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_table->weak_entries,通过哈希算法得到referent的索引,然后根据这个索引去weak_entries取出referent 跟这个传进来的referent看看是不是同一个。如果不是同一个就改变索引取出来继续做比较,如果找不到就返回,找到了就返回weak_entry_t结构体。

    外面拿到这个weak_entry_t *entry,执行append_referrer(entry, referrer),把这个referrer插入到entry里面。
    固若返回的这个entry是空的话,就会创建一个新的entry,然后分配空间,最后把这个entry插入weak_table里面的weak_entries数组里面。

    相关文章

      网友评论

        本文标题:weak指针实现的原理

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