美文网首页
弱指针weak的实现学习

弱指针weak的实现学习

作者: 我才是臭吉吉 | 来源:发表于2019-08-13 00:14 被阅读0次

    1. weak对象的实现函数

    首先,看个例子(取自《Objective-C高级编程 iOS与OS X多线程和内存管理》的第一部分):

    id __weak weakObj = [[NSObject alloc] init];
    

    其模拟代码为

    id obj;
    
    // 创建临时对象tmp
    id tmp = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(tmp, @selector(init));
    
    // 使用tmp创建weak对象obj
    objc_initWeak(&obj, tmp);
    
    // 作用域结束,tmp释放
    objc_release(tmp);
    
    // 释放weak对象obj
    objc_destroyWeak(&obj);
    

    通过代码可以看到,weak指针(weak对象)的创建及释放,实际上是通过objc_initWeak()objc_destroyWeak() 函数实现的**。现在,就需要查看一下iOS的运行时系统是如何实现weak的功能的。

    注:源代码采用objc.723版本

    objc_initWeak的实现:

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

    objc_destroyWeak的实现:

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

    补充一下,修改weak对象的函数objc_storeWeak的实现:

    id
    objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    

    可以看出,weak的相关功能,都是通过storeWeak函数进行实现的。通过不同的参数及条件设置实现weak对象的创建、修改和删除的**。

    2. storeWeak的实现

    还是不废话,直接贴出实现源码(删除了部分无关代码):

    /**
     更新weak变量。
     
     HaveOld为true时,即weak变量本身已经指向一个即将被清空的对象。这个值可以是nil。
     HaveNew为true时,即有个新对象需要赋值给weak指针。这个值可以为nil。
     CrashIfDeallocating为true时,如果newObj正在释放、或者newObj所属的类不支持弱引用,则进程就要中断(崩溃);如果CrashIfDeallocating为false,则意味着将nil存储到变量中。
     
     @param location __weak指针地址
     @param newObj  真正指向的对象
     */
    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        assert(haveOld  ||  haveNew); // 至少一个为true
        if (!haveNew) assert(newObj == nil); // 无新值时,newObj必须为nil
    
        id oldObj;
        // SideTable即为内存管理的数据结构。内部包含:引用计数表、弱引用表、自旋锁及相关方法。
        SideTable *oldTable; // 旧表(SideTable结构体指针)
        SideTable *newTable; // 新表(SideTable结构体指针)
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
    
        if (haveOld) {
            // 若存在旧值
            
            // 从weak地址中取出旧对象
            oldObj = *location;
            // 使用旧对象获取到旧表实例
            oldTable = &SideTables()[oldObj];
        } else {
            // 若不存在旧值
            
            // 旧表直接置为nil
            oldTable = nil;
        }
        
        
        if (haveNew) {
            // 若存在新值
            
            // 使用新值初始化得到新表实例
            newTable = &SideTables()[newObj];
        } else {
            // 若不存在新值
            
            // 新表直接置为nil
            newTable = nil;
        }
    
        // 根据haveOld和haveNew状态,给两张SideTable表进行对应加锁(自旋锁)
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        ...
    
        // Clean up old value, if any.
        if (haveOld) {
            // 若存在旧值
            
            // 在旧表中的weak表中清除相关信息(旧对象和weak指针的关联)
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // Assign new value, if any.
        if (haveNew) {
            // 若存在新值
            
            // 在新表的weak表中存储相关信息(使用newObj的地址作为key,location地址,即weak指针作为value)
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // weak_register_no_lock returns nil if weak store should be rejected
            // 如果被打断(如此时正在释放对象),则newObj就为nil了【注:crash的已经在上一步中进行处理了,这里是不crash的情况】
    
            // Set is-weakly-referenced bit in refcount table.
            if (newObj  &&  !newObj->isTaggedPointer()) {
                // 若已经绑定成功,且newObj不是taggedPointer
                
                // 将newObj的对应位标记为weak
                newObj->setWeaklyReferenced_nolock();
            }
    
            // Do not set *location anywhere else. That would introduce a race.
            // 确保weak指针指向newObj
            *location = (id)newObj;
        }
        else {
            // No new value. The storage is not changed.
            // 若没有新值,原存储信息不变
        }
        
        // 解锁相关表
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        // 返回newObj对象
        return (id)newObj;
    }
    

    在以上代码中可以看到,我们所有的操作(注册weak、移除weak等)都是发生在SideTable实例中的。且在操作时需要在有锁的条件下执行。我们看一下SideTable到底是何方神圣:

    struct SideTable {
        /** 自旋锁 */
        spinlock_t slock;
        /** 引用计数表(散列表) */
        RefcountMap refcnts;
        /** weak表(内部使用数组或二级表实现) */
        weak_table_t weak_table;
        
        ...
    }
    

    可以看到,SideTable即为内存管理的精髓。其内部包含的就是引用计数表和weak弱引用表

    SideTable是全局对象(不支持析构,会crash),是由分离锁进行管理的StripedMap对象:

    // 获取oldTable对象(根据oldObj对象)
    oldTable = &SideTables()[oldObj];
    

    其中,SideTables函数的实现为:

    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    

    在StripedMap模板类中,我们可以找到获取SideTable的相关实现:

    template<typename T>
    class StripedMap {
    
        enum { CacheLineSize = 64 };
    
    #if TARGET_OS_EMBEDDED
        enum { StripeCount = 8 };
    #else
        enum { StripeCount = 64 };
    #endif
    
        struct PaddedT {
            T value alignas(CacheLineSize);
        };
    
        PaddedT array[StripeCount];
    
        static unsigned int indexForPointer(const void *p) {
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            
            // hash算法(64位系统下得到的是0~63)
            return ((addr >> 4) ^ (addr >> 9)) % Stripe Count;
        }
    
     public:
        T& operator[] (const void *p) { 
            return array[indexForPointer(p)].value; 
        }
        
        ...
    }
    

    其中,public方法中的实现即说明了一切:通过对象地址得到的hash值,取出数组中保存的值(这里即为SideTable对象)。且通过hash算法还可知道,一个StripedMap对象在64位系统下包含64个子单元。可以理解为一个分离锁管理着64个对象的SideTable对象。

    由于本篇学习的是weak的实现,故我们暂时只讨论weak_table_t这个结构(这个还凑合,引用计数还根本不懂...)。

    3. weak弱引用表

    首先看一下weak_table_t的数据结构:

    /**
     * 全局弱引用表。
     * weak_entry_t内部使用原始对象作为key,
     * weak指针的地址的数组作为value
     */
    struct weak_table_t {
        /** 弱引用键值数据数组的起始地址 */
        weak_entry_t *weak_entries;
        /** weak_entry_t数组的元素个数 */
        size_t    num_entries;
        /** weak_entry_t数组的大小 */
        uintptr_t mask;
        /** hash查找最大偏移量 */
        uintptr_t max_hash_displacement;
    };
    

    weak_table_t内部保存着由week_entry_t实例组成的数组

    内部的weak_entry_t即为真正的键值对:key为引用的对象,value为weak指针组成的数组

    // 定义的结构体内部对于weak指针个数的储存上线
    #define WEAK_INLINE_COUNT 4
    
    // out_of_line_ness成员占用了inline_referrers数组index为1空间中的低2位。
    // inline_referrers[1]是一个指针对齐的DisguisedPtr的对象。
    // 一个指针对齐的DisguisedPtr的最低两位永远是0b00。
    // 因此,在out_of_line_ness == 0b10,就被用于标记out-of-line动态数组的状态。
    #define REFERRERS_OUT_OF_LINE 2
    
    struct weak_entry_t {
        /** 指向的对象 */
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                /** 存储weak指针的动态数组的起始地址 */
                weak_referrer_t *referrers;
                /** 标识是否使用了动态数组:占2位 */
                uintptr_t        out_of_line_ness : 2;
                /** 已存的weak指针个数:占62位 */
                uintptr_t        num_refs : PTR_MINUS_2;
                /** 动态数组的总大小 */
                uintptr_t        mask;
                /** hash查找最大偏移量 */
                uintptr_t        max_hash_displacement;
            }; // 五个成员,共32字节
            struct {
                // 结构体中自带的可以存储4个weak指针的静态数组
                // 注:out_of_line_ness域使用了索引为1的位置的最低2位
                weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
            }; // 32字节
        };
        
        /** 判定是否使用了动态数组存储weak指针 */
        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));
            return *this;
        }
    
        /** 创建新的weak键值对实例(以前没有) */
        weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
            : referent(newReferent)
        {
            // 使用静态数组存储weak指针
            inline_referrers[0] = newReferrer;
            // 将数组的其他位置数据置为nil
            for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
                inline_referrers[i] = nil;
            }
        }
    };
    

    weak_entry_t结构体固定为40字节大小。
    其中,为了快速访问weak指针,weak_entry_t中直接设置了一个包含4个元素的静态数组。当指向对象的weak指针超过上限时,就使用动态数组进行存储(将静态数组中的weak指针copy到动态数组中,之后再存入新的weak指针)。
    weak_entry_t提供给外部用于识别自身正在使用哪种存储数组的函数。

    有了以上这些知识,我们就可以解释SideTable中的weak表是如何注册weak对象并如何清除weak对象的过程了。

    4. 在weak引用表中注册weak对象

    /** 
     * 如果不存在,注册一个新的(对象,弱指针)键值对(entry对象)。
     * 
     * @param weak_table 全局弱引用表
     * @param referent 弱指针要指向的对象
     * @param referrer 弱指针的地址
     */
    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;
        // weak指针
        objc_object **referrer = (objc_object **)referrer_id;
    
        // 原对象为nil,或者原对象是taggedPointer,直接返回原对象
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // ensure that the referenced object is viable
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            // 若原始对象的类,没有实现内存管理的相关方法
            
            // 直接以SideTable的释放状态作为标识
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // 若原始对象的类,实现了内存管理的相关方法
    
            
            BOOL (*allowsWeakReference)(objc_object *, SEL) = 
                (BOOL(*)(objc_object *, SEL))
                object_getMethodImplementation((id)referent, 
                                               SEL_allowsWeakReference);
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                // 若allowsWeakReference的实现为消息转发函数,则证明调研对象不支持weak,直接返回nil
                return nil;
            }
            
            // 若allowsWeakReference的实现正常,执行此IMP,返回值取反作为释放的标识(即:自定义为不支持weak,则认为正在释放)
            deallocating =
                ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
        }
    
        if (deallocating) {
            // 根据释放标识,进行处理
            if (crashIfDeallocating) {
                // crash
                _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 {
                // 返回nil
                return nil;
            }
        }
    
        // now remember it and where it is being stored
        weak_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            // 在weak表中,使用原始对象获取生成入口信息,若存在(证明原始对象子表中已经存储了其他weak指针了)
            
            // 将weak指针插入到子表中
            append_referrer(entry, referrer);
        } 
        else {
            // 没有entry对象
            
            // 使用原始对象创建新的entry对象(绑定好key和value)
            weak_entry_t new_entry(referent, referrer);
            
            // 检查是否需要扩充weak表(扩充后,原始数据均插入了新weak表中)
            weak_grow_maybe(weak_table);
            
            // 将新的入口信息插入到weak表中
            weak_entry_insert(weak_table, &new_entry);
        }
    
    
        // 这里不要修改referrer的指向(也就是weak指针的指向不能变)
    
        // 返回原对象
        return referent_id;
    }
    

    其中,通过原始对象,在weak表中查找对应entry的过程如下:

    /** 
     * 根据给定的对象,返回弱引用表中对应的entry。
     * 如果没有对应的entry,返回NULL。
     * 执行了一次循环查找。
     *
     * @param weak_table 
     * @param referent The object. Must not be nil.
     * 
     * @return The table of weak referrers to this object. 
     */
    static weak_entry_t *
    weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
    {
        assert(referent);
    
        // 从表中取出weak_entry_t指针(weak数据表的起始地址)
        weak_entry_t *weak_entries = weak_table->weak_entries;
    
        if (!weak_entries) return nil;
    
        // 根据对象地址的hash与weak表的mask与运算得到遍历的起始index
        size_t begin = hash_pointer(referent) & weak_table->mask;
        size_t index = begin;
        size_t hash_displacement = 0;
        
        // 依次查看weak_entry_t对象中的referent是否为指向的对象
        while (weak_table->weak_entries[index].referent != referent) {
            // 不是,索引后移
            index = (index+1) & weak_table->mask;
            
            // 索引又变为begin了,直接crash(hash冲突且没有空余位置了)
            if (index == begin) bad_weak_table(weak_table->weak_entries);
            hash_displacement++;
            if (hash_displacement > weak_table->max_hash_displacement) {
                // 超出最大偏移范围,返回
                return nil;
            }
        }
        
        // 此时,通过index取到weak_entry_t对象,返回地址
        return &weak_table->weak_entries[index];
    }
    

    在查找遍历索引begin时,系统使用的方式是通过对象的hash值和weak_table的mask值进行按位与运算
    其中mask是num_entries-1,即如果此弱引用表的weak_entries个数是4,mask即为3,也就是0b11。
    对象地址hash之后,通过mask进行
    按位与
    运算得到的,就只有mask值对应的范围,即0b000b11,故index取值范围是03。系统巧妙地将对象地址转化为索引,在数组中查找对应位置的数据。
    后面使用while循环的原因是,由于hash值可能会重复,得到的index位置可能已经存在其他数据,故对index进行偏移处理,待新位置的元素符合要求,再进行操作。其中hash_displacement就是标记偏移量的。如果超过最大偏移量max_hash_displacement,则数组中没有符合要求的位置索引。

    向已经存在的weak_entry_t中插入新的weak指针,操作如下:

    static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
    {
        if (! entry->out_of_line()) {
            // 若使用的内部数组存储weak指针,则直接遍历,将空的赋值为weak指针地址
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i] == nil) {
                    entry->inline_referrers[i] = new_referrer;
                    return;
                }
            }
    
            // 内部数组已满,此时需要使用动态数组存储(先初始化4个地址空间)
            weak_referrer_t *new_referrers = (weak_referrer_t *)
                calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
            // 将内部数组的四个weak指针copy到动态数组中
            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());
    
        // 占用空间超过 3/4,扩容并插入新weak指针
        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;
        // 遍历找到待插入的索引位置
        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指针
        weak_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        // 计数+1
        entry->num_refs++;
    }
    

    而对于weak_table_t弱引用表中,没有引用对象指向的weak_entry_t对象时,直接创建新entry,将weak指针写入后,直接将此entry加入到弱引用表中。此过程与查询entry的过程非常相似:

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

    5. 在weak弱引用表中移除weak对象

    相对的,当指向的对象将要释放时,运行时系统会将注册到weak表中的原始对象相关的所有weak指针信息移除。也就实现了weak自动置为nil的功能。

    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        // 指向的对象
        objc_object *referent = (objc_object *)referent_id;
        // weak指针的地址
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
    
        // 指向对象不存在,直接返回(用指向对象的地址作为key进行保存)
        if (!referent) return;
    
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            // 使用指向的对象在weak表中查找,获取entry,如果存在
            
            // 在entry中移除weak指针的地址存储
            remove_referrer(entry, referrer);
            
            // 查看入口信息子表中是否还有其他weak指针存储
            bool empty = true;
            if (entry->out_of_line()  &&  entry->num_refs != 0) {
                // 使用了动态数组存储weak指针,且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指针地址存储了
                
                // 在weak表中移除entry信息
                weak_entry_remove(weak_table, entry);
            }
        }
    
        // 这里不要清除weak指针的地址,objc_storeWeak函数不允许他人更改
    }
    

    整体过程与注册weak指针的流程非常相似。查询到weak_entry_t对象后,移除内部的weak指针,最后清除此entry对象即可。

    移除entry中指定的weak指针的过程如下(与插入weak指针非常相似):

    static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
    {
        if (! entry->out_of_line()) {
            // 没有使用外部动态数组,即weak直接存储在内部静态数组中
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                // 查找到weak指针所在的对象
                if (entry->inline_referrers[i] == old_referrer) {
                    // 置为nil,完成清除(weak指针自动置nil的功能)
                    entry->inline_referrers[i] = nil;
                    return;
                }
            }
            // 未找到,出现内部错误
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    
        // weak指针存储超过了4个,使用了外部的动态数组
        
        // 得到遍历的起始索引
        size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        // 在动态数组中依次查看,直到找到weak指针存储的位置
        while (entry->referrers[index] != old_referrer) {
            index = (index+1) & entry->mask;
            if (index == begin) bad_weak_table(entry); // 出错crash
            hash_displacement++;
            if (hash_displacement > entry->max_hash_displacement) {
                _objc_inform("Attempted to unregister unknown __weak variable "
                             "at %p. This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             old_referrer);
                objc_weak_error();
                return;
            }
        }
        
        // 此时index所在位置即为weak指针存储的位置,置为nil清除
        entry->referrers[index] = nil;
        // 将索引个数-1
        entry->num_refs--;
    }
    

    在weak_table_t中移除weak_entry_t对象就比较简单了:

    static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
    {
        // 使用了动态数组存储weak指针,则直接从起始地址进行释放即完成清除(动态数组通过calloc申请的连续地址空间)
        if (entry->out_of_line()) free(entry->referrers);
        
        // 将entry的整个存储空间清零
        bzero(entry, sizeof(*entry));
    
        // 整体weak表的num_entries计数 - 1
        weak_table->num_entries--;
    
        // 检查缩小weak表的容量
        weak_compact_maybe(weak_table);
    }
    

    这里,weak_compact_maybe函数会检查weak表中entry的存储占用率,根据实际情况释放相应空间。

    不仅如此,weak_grow_maybe函数也如此,会根据占用率动态扩大存储空间。其内部实现都是通过weak_resize函数实现的。

    #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
    
    static void weak_resize(weak_table_t *weak_table, size_t new_size)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        weak_entry_t *old_entries = weak_table->weak_entries;
        
        // 分配内存,new_size * sizeof(weak_entry_t) 个字节(new_size * 40)
        weak_entry_t *new_entries = (weak_entry_t *)
            calloc(new_size, sizeof(weak_entry_t));
    
        weak_table->mask = new_size - 1; // 记录下尺寸
        weak_table->weak_entries = new_entries; // 数组起始地址
        weak_table->max_hash_displacement = 0;
        weak_table->num_entries = 0;  // restored by weak_entry_insert below
        
        // 将原始数据copy到新weak表中
        if (old_entries) {
            weak_entry_t *entry;
            weak_entry_t *end = old_entries + old_size;
            for (entry = old_entries; entry < end; entry++) {
                if (entry->referent) {
                    // 将原始数据插入到weak表中
                    weak_entry_insert(weak_table, entry);
                }
            }
            free(old_entries);
        }
    }
    

    这里,我们就知道了,weak_table_t中的weak_entries成员实际上是weak_entry_t组成的数组(calloc创建的连续内存空间)。

    6. 总结

    至此,我们基本了解了创建和释放一个weak弱指针对象所需要做的主要工作。也明白了weak指针在原对象释放后自动置为nil的实现方式。

    此外,我们还了解了:

    1. 苹果对于将hash值转化为数组索引index的转换方式及冲突后的索引偏移的操作。
    2. 对于weak_entry_t数据结构中,存储weak指针的两种方式(快速量少用静态数组,量大使用动态数组的可伸缩方式)的分段式存储思想。

    7. 参考资料:

    相关文章

      网友评论

          本文标题:弱指针weak的实现学习

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