OC Runtime之Weak(2)---weak_entry_

作者: 08a2d4bd26c9 | 来源:发表于2017-08-08 15:26 被阅读44次

    上一篇文章主要介绍了弱引用的基本概念,以及weak_table_t的基本结构和实现。weak_entry_t是weak_table_t具体存储的数据类型,本文继续深挖,探究weak_entry_t的数据结构和实现。

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

    DisguisedPtr是runtime对于普通对象指针(引用)的一个封装,目的在于隐藏weak_table_t的内部指针。其定义如下:

    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    

    可以看出,weak_entry_t 的结构和weak_table_t有一点点类似,基本也是一个HashTable的实现。num_refs,mask和max_hash_displacement都在weak_table_t中出现过,作用也是基本相同,不再详细介绍。

    weak_entry_t有一个巧妙的设计,即如果一个对象对应的弱引用数目较少的话(<=WEAK_INLINE_COUNT,runtime把这个值设置为4),则其弱引用会被依次保存到一个inline数组里。这个inline数组的内存会在weak_entry_t初始化的时候一并分配好,而不是需要用到的时候再去申请新的内存空间,从而达到提到运行效率的目的。此外,union中的两个struct是共享同一块内存的,如果不使用inline数组,而直接使用HashTable的方式来实现,那么num_refs,mask和max_hash_displacement这些变量都需要单独的存储空间,会使用更多的内存。综上,使用inline数组在节约一定内存空间的同时还相对提高了运行效率。

    out_of_line_ness占用的是inline_referrers[1]最低两位的空间,也是weak_entry_t当前使用inline数组还是outline数组(也就是HashTable的实现)的标记位。由于DisguisedPtr的最低两个bit保证是00或者11(DisguisedPtr的实现此处不不表,不影响对于weak_entry_t的理解),所以如果out_of_line_ness不是0的话,也就意味着weak_entry_t已经不再使用inline数组。这一点从out_of_line函数的实现可以得到证实,需要判断out_of_line_ness的值等于REFERRERS_OUT_OF_LINE。REFERRERS_OUT_OF_LINE在runtime中被设置为2,可以被两个bit来存储表示。

    对weak_entry_t主要操作的函数是grow_refs_and_insert和append_referrer两个。上一篇文章提到的weak_table_t中有一个weak_register_no_lock函数,其中就调用了append_referrer函数来为一个对象保存新的弱引用。

    static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
    {
        if (! entry->out_of_line()) {
            // Try to insert inline.
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i] == nil) {
                    entry->inline_referrers[i] = new_referrer;
                    return;
                }
            }
    
            // Couldn't insert inline. Allocate out of line.
            weak_referrer_t *new_referrers = (weak_referrer_t *)
                calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
            // This constructed table is invalid, but grow_refs_and_insert
            // will fix it and rehash it.
            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());
    
        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_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        entry->num_refs++;
    }
    
    • append_referrer函数首先处理weak_entry_t还在使用inline数组的情况。首先尝试像inline数组中插入一个新的弱引用,如果inline数组已满,那就创建一个WEAK_INLINE_COUNT大小的新数组,改用outline的方式,将inline数组中的元素依次拷贝过来。
    • 函数的后半部分处理使用outline数组的情况,如果outline数组的使用率在75%及以上,那么调用grow_refs_and_insert函数进行扩充,并且插入新的弱引用。否则就直接进行hash运算插入,过程和weak_table_t的插入过程基本相同。
    __attribute__((noinline, used))
    static void grow_refs_and_insert(weak_entry_t *entry, 
                                     objc_object **new_referrer)
    {
        assert(entry->out_of_line());
    
        size_t old_size = TABLE_SIZE(entry);
        size_t new_size = old_size ? old_size * 2 : 8;
    
        size_t num_refs = entry->num_refs;
        weak_referrer_t *old_refs = entry->referrers;
        entry->mask = new_size - 1;
        
        entry->referrers = (weak_referrer_t *)
            calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
        entry->num_refs = 0;
        entry->max_hash_displacement = 0;
        
        for (size_t i = 0; i < old_size && num_refs > 0; i++) {
            if (old_refs[i] != nil) {
                append_referrer(entry, old_refs[i]);
                num_refs--;
            }
        }
        // Insert
        append_referrer(entry, new_referrer);
        if (old_refs) free(old_refs);
    }
    
    • 函数首先对outline数组进行扩充,容量是原来的两倍。而后依次将老数组中的元素hash插入到新数组中,最终hash插入新的引用。
    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;
                }
            }
            _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;
        }
    
        size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        while (entry->referrers[index] != old_referrer) {
            index = (index+1) & entry->mask;
            if (index == begin) bad_weak_table(entry);
            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;
            }
        }
        entry->referrers[index] = nil;
        entry->num_refs--;
    }
    
    • remove_referrer函数负责删除一个弱引用。函数首先处理inline数组的情况,直接将对应的弱引用项置空。如果使用了outline数组,则通过hash找到要删除的项,并直接删除。过程和weak_table_t对应的操作基本相同。

    至此,weak_entry_t的数据结构和操作都介绍完毕。可以看出苹果对于程序的效率以及内存优化还是花费了很大心思的。后续会介绍NSObject实现中和弱引用有关联的部分,这些加起来构成了整个弱引用机制。

    相关文章

      网友评论

        本文标题:OC Runtime之Weak(2)---weak_entry_

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