OC Runtime之Weak(2)---weak_entry_t

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

struct weak_entry_t {
    DisguisedPtr 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 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_t)