美文网首页
weak的底层原理

weak的底层原理

作者: 今年27 | 来源:发表于2022-08-08 10:56 被阅读0次

    在项目中我们经常用到weak指针,其可以保证在指向的对象释放后,weak指针自动置为nil,以防止崩溃,因为在OC中向nil发送消息是没有任何处理的。通过__weakproperty weak等形式,都可以将指针修饰为weak类型的。

    weak的实现原理其实很简单,概括来说就是,在内存中有一个名为weak_table_t的哈希表,weak_table_t中存储着App所有的weak对象及指针。当有对象被weak指针修饰时,会将被修饰的对象及指针添加到weak_table_t表中。当被weak指针的作用域消失时,weak指针会被销毁,随后会 从哈希表中查找对应的weak指针,并将指针置为nil

    weak引用表

    SideTables

    weak的实现中很多地方都用到了SideTables函数,此函数内部直接返回了一个哈希表,哈希表名为SideTablesMap。此函数用来保存整个程序所有被weak指针指向的对象,和应用程序是一对一的。每个对象对应其中的一个元素,例如两个weak指针指向一个对象,则这个对象对应一个SideTable对象,这个对象中保存这两个weak指针。

    按照比较新的runtime 779.1版本,对于SideTables的定义如下。SideTables本质上是一个静态函数,其内部是通过SideTablesMap实现的。

    static StripedMap<SideTable>& SideTables() {
        return SideTablesMap.get();
    }
    
    

    下面定义是一个嵌套模型,其中SideTablesMap是一个objc命名空间下的ExplicitInit类,其内部实现的上面的get函数。StripedMap也是一个模型,传入的SideTable就是全局的weak表所在的结构体。

    static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
    
    

    ExplicitInit

    ExplicitInit是一个模板类,传入的type就是上面定义的StripedMap。在get函数中有reinterpret_cast关键字,这个关键字类似于强制类型转换,改变类型但存储的数据不变。

    template <typename Type>
    class ExplicitInit {
        alignas(Type) uint8_t _storage[sizeof(Type)];
    
    public:
        template <typename... Ts>
        void init(Ts &&... Args) {
            new (_storage) Type(std::forward<Ts>(Args)...);
        }
    
        Type &get() {
            return *reinterpret_cast<Type *>(_storage);
        }
    };
    
    

    StripedMap

    我们继续来看StripedMap模板的定义:

    template<typename T>
    class StripedMap {
    #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR //如果是iphone设备Map的数量为8
        enum { StripeCount = 8 };
    #else
        enum { StripeCount = 64 };
    #endif
    
        struct PaddedT {
            T value alignas(CacheLineSize); //T value 64字节对齐
    
        };
    
        PaddedT array[StripeCount];// 所有PaddedT struct 类型数据被存储在array数组中(这里的PaddedT 带入代码中即 SideTable)。
    
        static unsigned int indexForPointer(const void *p) {// index的hash算法,该方法以void *作为key 来获取void *对应在StripedMap 中的位置
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
        }
    //苹果为array数组写了一些公共的存取数据的方法,主要是调用indexForPointer方法,使得外部传入的对象地址指针直接hash到对应的array节点,这里主要是对&符号重写,所以我们经常看到&SideTables()[obj]这种调用操作,实际上就是调用这个array[indexForPointer(p)].value从而获取SideTable
     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]; 
        }
    
        // Shortcuts for StripedMaps of locks.
        void lockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.lock();
            }
        }
    
        void unlockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.unlock();
            }
        }
    
        void forceResetAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.forceReset();
            }
        }
    
        void defineLockOrder() {
            for (unsigned int i = 1; i < StripeCount; i++) {
                lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
            }
        }
    
        void precedeLock(const void *newlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
        }
    
        void succeedLock(const void *oldlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(oldlock, &array[0].value);
        }
    
        const void *getLock(int i) {
            if (i < StripeCount) return &array[i].value;
            else return nil;
        }
        
    #if DEBUG
        StripedMap() {
            // Verify alignment expectations.
            uintptr_t base = (uintptr_t)&array[0].value;
            uintptr_t delta = (uintptr_t)&array[1].value - base;
            ASSERT(delta % CacheLineSize == 0);
            ASSERT(base % CacheLineSize == 0);
        }
    #else
        constexpr StripedMap() {}
    #endif
    };
    
    

    SideTable

    SideTable结构体是weak实现的核心,结构体中定义了引用计数表和弱引用表,弱引用使用weak_table字段。下面的slock自旋锁,两个表都会使用同一个锁。

    struct SideTable {
        // 自旋锁,保证线程安全
        spinlock_t slock;
        // 引用计数表,在未开启isa指针优化,或isa指针存储满了才会用
        RefcountMap refcnts;
        // 弱引用表
        weak_table_t weak_table;
    };
    
    

    weak_table_t

    weak_table_t是弱引用表,所有的弱引用都会被存储在这个表中。在下面结构体变量的定义中,weak_entries是一个哈希表的结构,其中key是堆区内存地址,通过key可以获取weak_entries链表中对应的weak_entry_t,也就是指针数组。

    struct weak_table_t {
        // 弱引用数组,用来存储weak_entry_t对象,是一个链表结构
        weak_entry_t *weak_entries;
        // 弱引用数组大小,如果到阈值会自动扩容
        size_t    num_entries;
        // 进行哈希运算的mask,大小是num_entries-1
        uintptr_t mask;
        // 最大冲突数,一般不会大于这个数
        uintptr_t max_hash_displacement;
    };
    
    

    weak_entry_t

    weak_entry_t结构体内部的定义是一个union联合体,联合体中包含两个结构体,此结构体用来存储指针地址。在存储时会判断,如果同一个对象的weak指针数量少于4,则使用inline_referrers定长数组,否则使用referrers动态长度数组。

    有趣的是,因为是union联合体,当weak指针数量大于4之后,不需要另外开辟空间,在当前空间直接覆盖inline_referrers的存储区域,使用referrers哈希表。

    #define WEAK_INLINE_COUNT 4
    
    struct weak_entry_t {
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                // 弱引用该对象的,指针地址的哈希数组
                weak_referrer_t *referrers;
                // 是否使用动态长度数组
                uintptr_t        out_of_line_ness : 2;
                // referrers数组中的元素
                uintptr_t        num_refs : PTR_MINUS_2;
                // 参与哈希运算的值,是referrers数组分配的长度,会随着动态扩容而改变
                uintptr_t        mask;
                uintptr_t        max_hash_displacement;
            };
            struct {
                weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
            };
        };
    };
    
    

    weak_referrer_t

    无论是inline_referrers数组还是referrers链表,都是一个weak_referrer_t的定义,在这个typedef中定义了一个指向objc_object的指针,isa指针都是被优化为objc_object的,所以这个指针指向这个被优化的isa。也可以简单理解为,weak_referrer_t本质上存储着被weak修饰的指针地址。

    结合weak_entry_t的定义,可以知道referrers数组存储结构,就是直接将DisguisedPtr转换后的整型负数,存储在referrers数组中。

    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    
    

    DisguisedPtr

    DisguisedPtr是一个模板工具类,主要起到将指针和整型相互转换的作用,目的是为了隐藏指针,起到一个伪装的作用。隐藏指针的作用在于,一方面是为了安全性,另一方面也防止leaks这些检测工具的误判。

    template <typename T>
    class DisguisedPtr {
        // 存储地址转换为整数的结果
        uintptr_t value;
        // 将地址转为整数,并取反
        static uintptr_t disguise(T* ptr) {
            return -(uintptr_t)ptr;
        }
        // 将转换为负数的地址,先取反随后转换为地址
        static T* undisguise(uintptr_t val) {
            return (T*)-val;
        }
    
     public:
        // 构造函数
        DisguisedPtr() { }
        // 通过指针,初始化value
        DisguisedPtr(T* ptr) 
            : value(disguise(ptr)) { }
        DisguisedPtr(const DisguisedPtr<T>& ptr) 
            : value(ptr.value) { }
        // 运算符重载,将指针转换为DisguisedPtr的过程,直接由“&”取地址符来实现
        DisguisedPtr<T>& operator = (T* rhs) {
            value = disguise(rhs);
            return *this;
        }
        DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
            value = rhs.value;
            return *this;
        }
    
        operator T* () const {
            return undisguise(value);
        }
        // 运算符重载,将地址转为指针
        T* operator -> () const { 
            return undisguise(value);
        }
        T& operator * () const { 
            return *undisguise(value);
        }
        T& operator [] (size_t i) const {
            return undisguise(value)[i];
        }
    };
    
    

    创建weak指针

    objc_initWeak

    通过__weak等形式创建的指针,编译器会将其转换为objc_initWeak函数的调用,并将被指向对象及weak指针传进去。但需要注意的是,此函数并不是线程并发安全的,所以需要注意多线程的使用。

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

    在调用函数后会将haveNewnewObj等参数传入,供storeWeak调用。

    storeWeak

    无论是创建weak指针还是销毁weak指针,其内部实现都是通过storeWeak函数实现的。storeWeak函数是一个C++的模板函数,函数会传入五个参数。其中haveOldhaveNew是互斥的,haveNew则表示新创建的weak指针,haveOld则表示将已有的weak指针置为nil,或将指针重定向。

    由于storeWeak函数中的代码量较大,所以只保留了核心代码,不重要的代码都删掉了,看的也清楚点。如果是通过objc_initWeak调用进来,下面template的三个bool参数定义,分别如下。

    • haveOld = false
    • haveNew = true
    • crashIfDeallocating = true
    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        if (!haveNew) assert(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
    
     retry:
        // 判断location是否已经指向weak对象,有的话先把旧对象取出来
        if (haveOld) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        // 将新传入的对象,对应的weak表取出来,如果这个对象之前被weak指针指向过,则返回有值
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
    
        // 如果有旧对象,但是不是location所指向的,也就是当前传入的对象。则表示从retry执行到这里,可能被其他线程改过,所以需要重新执行retry
        if (haveOld  &&  *location != oldObj) {
            goto retry;
        }
    
        // 有新对象,则判断类是否初始化,没有初始化则先初始化,再重新执行retry
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
                previouslyInitializedClass = cls;
                goto retry;
            }
        }
    
        // 如果location指针指向过其他对象,则执行weak_unregister_no_lock,将weak表和旧对象解绑
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // 将传入的新对象,以及新对象的指针地址,添加到weak表中
        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;
        }
    
        return (id)newObj;
    }
    
    

    storeWeak函数中会进行判断,如果是haveNew则表示创建一个weak指针,这时会判断被指向的对象是否第一次传入,如果是则会创建新的内存空间,如果是第二次被weak指向则取出之前已经创建的weak_table。当释放weak指针时也是如此,先取出SideTable对象再从weak_table中移除对应的weak指针。

    随后会进入一个判断,会判断弱引用对象所属的类对象是否未执行+initialized方法,如果未执行则调用下面的代码初始化类对象。以保证在弱引用对象和+initialized方法之间,不会产生死锁。

    最后会进入具体的实现中,这些实现代码都在objc-weak.mm中。移除weak引用或指针重定向会通过weak_unregister_no_lock函数实现,添加新的weak引用则会通过weak_register_no_lock函数实现。

    weak_register_no_lock

    weak_register_no_lock函数是添加weak引用的关键,在函数中会有是否可以进行weak操作的判断,如果不符合条件则return或抛出异常。需要注意的是,在NSObject.mm文件中提供了allowsWeakReference方法,可以通过此方法返回是否允许使用weak引用,在下面的代码中也会向此方法发送消息来做确认。

    weak_register_no_lock函数传入的四个参数如下。

    • weak_table,全局的weak哈希表
    • referent_idweak指针。
    • *referrer_idweak指针地址。
    • crashIfDeallocating,如果weak指向的地址正在释放中,是否crash
    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;
        // 判断传入的对象是否为空,以及是否tagged pointer这些边界判断
        if (!referent || referent->isTaggedPointer()) return referent_id;
    
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // 有自定义的retain实现,执行自定义方法
            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("error");
            } else {
                return nil;
            }
        }
    
        // 判断堆区内存是否有被weak引用过,如果有的话就加入到已有的weak_entry_t中
        weak_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            append_referrer(entry, referrer);
        } 
        else {
            // 如果没有,则创建一个weak_entry_t,并插入到全局的weak_table哈希表中
            weak_entry_t new_entry(referent, referrer);
            weak_grow_maybe(weak_table);
            weak_entry_insert(weak_table, &new_entry);
        }
        return referent_id;
    }
    
    

    在函数中通过referent获取到被weak引用的对象,通过referrer获取到指针对象,随后会将这两个变量包装成weak_entry_t结构体,并调用对应的函数添加到哈希表中。

    执行添加操作的有两个关键函数,当对象第一次有weak指针指向时,会调用weak_entry_insertweak_entry_t插入到哈希表中,后面的weak指向都会调用append_referrer函数向表中添加referrer

    weak_entry_for_referent

    weak_entry_for_referent函数用来查找referentweak指向的堆区地址,也就是objc_object对象的地址。随后通过hash_pointer函数找到referent所在的index,也就是哈希表的key。在while循环中遍历weak_entries表,从index开始查找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;
    
        // 通过哈希算法获取到referent的index,也就是所在的下标
        size_t begin = hash_pointer(referent) & weak_table->mask;
        size_t index = begin;
        size_t hash_displacement = 0;
        // 循环时从index所在的下标开始,这样查找速度会很快
        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];
    }
    
    

    append_referrer

    此函数是对weak_entry_t进行插入,向数组后面拼接指向指针的地址。内部会根据哈希表大小,决定用动态数组还是定长数组,并且会根据使用情况,对动态数组进行扩容。并最终插入到weak_entry_t的末尾。

    static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
    {
        // 如果还未开启动态长度数组,则进入这里
        if (! entry->out_of_line()) {
            // 先尝试定长数组,如果有空位置就插入
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i] == nil) {
                    entry->inline_referrers[i] = new_referrer;
                    return;
                }
            }
    
            // 如果定长数组存满了,就创建动态数组,并将定长数组的对象插入进来
            weak_referrer_t *new_referrers = (weak_referrer_t *)
                calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
    
            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());
    
        // 判断weak_entry_t已经使用的大小,是否超过了已经开辟的3/4,如果超过则进行动态扩容
        if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
            return grow_refs_and_insert(entry, new_referrer);
        }
    
        // 通过哈希算法查找当前的index
        size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        // 通过while循环,找到referrers数组的最新一个为空的位置
        while (entry->referrers[index] != nil) {
            hash_displacement++;
            // 由于扩容是成倍扩容的,所以mask的值一定是,0x111, 0x1111, 0x11111
            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;
        }
        // 将new_referrer插入到新的空位置,并将count加一
        weak_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        entry->num_refs++;
    }
    
    

    weak_entry_insert

    weak_table_t中定义了weak_entries结构体变量,这是一个数组结构,弱引用对象都保存在这里。当插入一个weak_entry_t时会遍历数组,并找到合适的位置将其插入。

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

    指针重定向

    有时候会把weak指针指向一个新的对象,这时候会涉及到指针重定向。新建weak指针会传入newObj并调用对应的函数,weak指针作用域销毁会传入oldObj并调用对应的函数,而指针重定向则这两个参数都有值,haveNewhaveOld也都是有值的。

    指针重定向会在一次storeWeak函数调用中处理,并按顺序先调用weak_unregister_no_lock函数,先将weak指针从哈希表中移除,再调用weak_register_no_lock函数添加到新的SideTable中。

    dealloc

    rootDealloc

    当对象释放时,会调用dealloc方法,并调用到rootDealloc来实现释放逻辑。在释放时会判断,如果没有弱引用等逻辑,可以直接调用free函数释放,否则调用object_dispose函数处理释放逻辑。

    inline void
    objc_object::rootDealloc()
    {
        if (isTaggedPointer()) return;
        // 如果符合下面条件,则直接调用free函数释放内存
        if (fastpath(isa.nonpointer  &&  
                     !isa.weakly_referenced  &&  
                     !isa.has_assoc  &&  
                     !isa.has_cxx_dtor  &&  
                     !isa.has_sidetable_rc))
        {
            assert(!sidetable_present());
            free(this);
        } 
        else {
            object_dispose((id)this);
        }
    }
    
    

    objc_destructInstance

    object_dispose函数中调用的objc_destructInstance函数,在objc_destructInstance函数中,执行了一些释放和收尾的工作。而weak的释放操作,就在clearDeallocating中完成的。

    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            if (cxx) object_cxxDestruct(obj);
            if (assoc) _object_remove_assocations(obj);
            obj->clearDeallocating();
        }
    
        return obj;
    }
    
    

    clearDeallocating

    clearDeallocating函数中,会判断是否开启指针优化,如果未开启则执行sidetable的释放逻辑。如果开启了指针优化,并且有weak指针,或引用计数的逻辑,则执行clearDeallocating_slow函数。

    inline void 
    objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    
    

    clearDeallocating_slow函数内部,实际上通过weak_clear_no_lock函数对weak指针释放逻辑进行的实现。

    weak_clear_no_lock

    weak最核心的功能,即对象释放时,将weak指针指向nil。这个逻辑就在weak_clear_no_lock函数中实现的。

    void 
    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        objc_object *referent = (objc_object *)referent_id;
        // 根据被释放的对象referent_id,从weak_table中找到weak_entry_t结构体
        weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
        if (entry == nil) {
            return;
        }
    
        weak_referrer_t *referrers;
        size_t count;
    
        // 判断用的是定长数组,还是动态数组,并将数组赋值给referrers指针
        if (entry->out_of_line()) {
            referrers = entry->referrers;
            count = TABLE_SIZE(entry);
        } 
        else {
            referrers = entry->inline_referrers;
            count = WEAK_INLINE_COUNT;
        }
    
        // 遍历weak数组,并将指向weak堆区地址的指针,都置为nil
        for (size_t i = 0; i < count; ++i) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                if (*referrer == referent) {
                    *referrer = nil;
                }
                else if (*referrer) {
                    _objc_inform("__weak variable at %p holds %p instead of %p. "
                                 "This is probably incorrect use of "
                                 "objc_storeWeak() and objc_loadWeak(). "
                                 "Break on objc_weak_error to debug.\n", 
                                 referrer, (void*)*referrer, (void*)referent);
                    objc_weak_error();
                }
            }
        }
    
        // 遍历结束后,从weak_table哈希表中,将weak_entry_t移除
        weak_entry_remove(weak_table, entry);
    }
    
    

    释放优化

    objc_destroyWeak

    weak作为一个局部变量出现时,编译器会对weak进行优化。创建对象后,会通过objc_initWeak的方式将weak指针添加到哈希表中。在作用域结束时,会通过objc_destroyWeakweak直接释放掉。

    weak指针作用域消失时,系统会调用objc_destroyWeak函数来处理,并在函数内部调用storeWeak函数。随后storeWeak函数中会传入haveOld,表示是执行移除weak的操作。

    void objc_destroyWeak(id *location)
    {
        (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
            (location, nil);
    }
    
    

    weak_unregister_no_lock

    weak_unregister_no_lock函数是在storeWeak函数中调用的,当weak指针第一次指向或重新指向时,都会调用storeWeak函数。如果重新指向,会调用weak_unregister_no_lock先将之前的移除掉。

    函数会通过referent获取到被weak引用的对象,通过referrer获取到指针对象。随后会通过weak_entry_for_referent函数查找被指向的对象是否有weak_entry_t,如果找到则进入if语句中。

    if语句中会调用remove_referrer函数,将被weak指针从weak_entry_t中移除,并将weak指针置nil,这步就是最关键的一步了。随后会判断,如果被指向的对象,所有weak指针都没有了,则将对象从weak_table的哈希表中移除。

    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中获取weak_entry_t对象,如果有的话则进入if语句
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            // 从weak_entry_t中删除referrer引用
            remove_referrer(entry, referrer);
            // 判断删除后,weak_entry_t是否还有弱引用指针指向这块内存地址
            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;
                    }
                }
            }
            // 如果为空,则将weak_entry_t从全局weak_table哈希表中删除
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    }
    
    

    remove_referrer

    这是weak实现的关键函数,和向weak_entry_t中添加一样,只是这个过程是逆向的。会先遍历inline_referrers数组中有没有指针对象,如果有则将指针置为nil,否则就查找referrers数组。

    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;
        while (entry->referrers[index] != old_referrer) {
            index = (index+1) & entry->mask;
            if (index == begin) bad_weak_table(entry);
        }
        entry->referrers[index] = nil;
        entry->num_refs--;
    }
    
    weak底层原理

    相关文章

      网友评论

          本文标题:weak的底层原理

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