• Objective-C中weak实现原理


    Objective-C的对象采用引用计数来管理内存,如果对象被强持有,这个对象的引用计数会增加,如果对象被弱持有,这个对象的引用计数不会增加。弱持有也就是weak如何实现的呢?首先看下weak相关底层实现用到的数据结构,主要分为SideTable,weak_table_t和weak_entry_t这几个数据结构。

    struct SideTable {
        spinlock_t slock;   //锁
        RefcountMap refcnts;  //引用计数表
        weak_table_t weak_table;  //weak引用表
    };  
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SideTable是Objective-C中的引用计数表,它持有了weak引用表,也就是weak_table_t类型的weak_table。

    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;  //弱引用项数量
        uintptr_t mask;         //用于计算哈希的mask
        uintptr_t max_hash_displacement;  //允许的哈希未命中的次数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在weak_table_t中,持有了weak_entry_t指针,这个指针指向一个数组,数组中每个weak_entry_t代表着一个弱引用项,这个数组模拟实现了hash。

    struct weak_entry_t {
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                weak_referrer_t *referrers;     //weak指针数组
                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];
            };
        };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    一个弱引用项持有了referent,也就是被引用的对象,接下来使用联合体union持有了weak_referrer_t类型的数组,当weak指针指向的数组不超过4个元素时,使用定长的数组,超过4个元素的时候,使用变长数组。

    这里weak_entry_t并没有直接持有被引用的对象,而是持有了DisguisedPtr类型的对象。

    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() { }
        DisguisedPtr(T* ptr) 
            : value(disguise(ptr)) { }
        DisguisedPtr(const DisguisedPtr<T>& ptr) 
            : value(ptr.value) { }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    DisguisedPtr的核心方法是disguise和undisguise,disguise所做的事情是把指针的值加了负号,undisguise再次添加负号得到原始数据。做这一层包装替换的原因是防止leaks工具把对象检测为内存泄露。

    整体的数据结构大致就是这样,SideTable持有weak_table_t类型的指针,weak_table_t持有

    weak_entry_t类型的指针。weak_table_t代表的是weak表,里面存放很多weak指针和weak指针指向的对象,weak_entry_t代表的weak引用项,它持有了DisguisedPtr处理过的对象的指针和weak指针。weak_entry_t在底层是使用数组存储起来的,为了加快访问查找速度,在数组的数据结构上,模拟实现了hash表。

    了解了weak引用相关的数据结构之后,可以看下weak实现的过程。当我们使用weak指针,使weak指针指向一个对象后,会执行到objc_storeWeak方法。

    id objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
    
        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还没有初始化,先进行初始化
                class_initialize(cls, (id)newObj);
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // 如果weak指针有旧值,先清理旧值
        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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
            
    
            // 设置weak引用标记位
            if (!_objc_isTaggedPointerOrNil(newObj)) {
                newObj->setWeaklyReferenced_nolock();
            }
            //把新值复制给*location,weak指针指向newObj
            *location = (id)newObj;
        }
        else {
            // No new value. The storage is not changed.
        }
        
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        callSetWeaklyReferenced((id)newObj);
    
        return (id)newObj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    objc_storeWeak函数调用了storeWeak函数,storeWeak函数中的核心方法是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;
        objc_object **referrer = (objc_object **)referrer_id;
        //如果是taggedPointer,直接返回referent_id
        if (_objc_isTaggedPointerOrNil(referent)) return referent_id;
    
        // 查看引用的对象是否在释放,如果在释放,返回nil或者crash
        if (deallocatingOptions == ReturnNilIfDeallocating ||
            deallocatingOptions == CrashIfDeallocating) {
            bool deallocating;
            if (!referent->ISA()->hasCustomRR()) {
                deallocating = referent->rootIsDeallocating();
            }
            else {
                // Use lookUpImpOrForward so we can avoid the assert in
                // class_getInstanceMethod, since we intentionally make this
                // callout with the lock held.
                auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
                lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                           referent->getIsa());
                if ((IMP)allowsWeakReference == _objc_msgForward) {
                    return nil;
                }
                deallocating =
                ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
            }
    
            if (deallocating) {
                if (deallocatingOptions == 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;
                }
            }
        }
    
        // 查找weak_entry_t,并且追加weak指针
        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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    weak_register_no_lock函数的核心在于通过weak_entry_for_referent查找弱引用项,如果已经有了对应的weak_entry_t,直接插入weak指针到数据里面。如果没有则新建new_entry对象,并且加入到weak_entry_t指针指向的数组里面。

    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];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    weak_entry_for_referent的实现比较简单,从weak_table->weak_entries得到weak_entry_t指针,通过

    hash_pointer(referent) & weak_table->mask得到哈希的索引,如果有哈希冲突,可以增加索引值,继续查找。可以看出weak_entries实际上是数组的数据结构,为了加快查找速度,在数组上实现了hash。

    static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
    { 
       
        if (! entry->out_of_line()) {
             //weak指针数量不超过4个
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i] == nil) {
                    entry->inline_referrers[i] = new_referrer;
                    return;
                }
            }
    
            //weak指针数量超过4个,开辟内存存储weak指针
            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;
        }
    
        //weak指针数量超过4个
        ASSERT(entry->out_of_line());
        
        //数组扩容
        if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
            return grow_refs_and_insert(entry, new_referrer);
        }
        size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        //找到一个为nil的位置,然后插入new_referrer
        while (entry->referrers[index] != nil) {
            hash_displacement++;
            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;
        }
        weak_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        entry->num_refs++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    append_referrer函数用于处理weak_entry_t内部的weak指针,这里也同样使用了数组模拟hash,构建了hash表存储weak指针。如果一个对象的weak指针数量不超过4个, 直接使用定长数组插入。如果超过4个,找到为nil的位置并且传入new_referrer。

    以上内容是weak指针添加的过程,还有一个weak指针移除的方法。

    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;
         
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            //移除referrer指针
            remove_referrer(entry, referrer);
            //如果没有weak指针指向这个对象,需要移除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;
                    }
                }
            }
    
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    weak_unregister_no_lock用于移除一个weak指针,它首先查找对象对应的weak_entry_t,然后找到weak指针哈希表,移除这个weak指针,移除后要判断对象的weak指针是不是已经被清空了,如果被清空了,对应的weak_entry_t需要移除。

  • 相关阅读:
    django 数据库 get_or_create函数update_or_create函数
    AI:73-结合语法知识的神经机器翻译研究
    【学习记录】卷积神经网络常用概念
    为什么要有包装类,顺便说一说基本数据类型、包装类、String类该如何转换?
    redis的string类型及bitmap
    JAVA 编程中的SOLID设计原则
    Java Number & Math 类
    有关于MySQL的面试题
    京东一面Redis 如何实现库存扣减操作?如何防止商品被超卖
    【ERROR】ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND No package.json
  • 原文地址:https://blog.csdn.net/u011608357/article/details/128168509