美文网首页iOSMore Stronger程序员
OC Runtime之Weak(1)---weak_table_

OC Runtime之Weak(1)---weak_table_

作者: 08a2d4bd26c9 | 来源:发表于2017-08-03 18:38 被阅读145次

    众所周知,weak,strong都是OC的修饰符,然而两者有很大的区别。strong会导致引用计数变化,而weak则不会。与此同时,weak引用在被指向的对象析构后,会自动的变为nil而不是野指针,从而可以避免程序崩溃。那么weak在runtime层是如何实现的呢?本系列文章会结合源码,详细分析weak的实现。

    抛开苹果开源的runtime源码不谈,我们可以首先思考一下,如果需要我们来实现一套weak机制,那应该如何实现呢?假设目前有个object O,有一个weak引用W指向O,在O析构后,W会指向nil,说明runtime使用了某种数据结构记录的W和O的映射关系。这种映射关系应该是一对多的,即一个weak引用只能指向一个object,而一个object可以同时有很多weak引用指向。可以支持一对多映射的数据结构有很多,但如何快速的通过object O,找到其对应的一个或多个weak引用呢?无疑,HashTable是最优的解决方式,可以在O(1)的时间复杂度内进行快速查找。果不其然,在我们查看源码后发现,苹果正是使用了HashTable来维护对象和弱引用的映射关系。

    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    

    上面的weak_table_t是一个c语言的结构体,是runtime对于HashTable的具体实现。weak_entry_t也是一个c语言的结构体,是weak_table_t中具体存储的值的类型,负责记录一个对象和其弱引用的对应关系。weak_entry_t的结构本文暂且不表,不影响对于weak_table_t的理解。num_entries负责记录weak_table_t目前保存的entry的数目,mask记录weak_table_t的总容量,max_hash_displacement记录weak_table_t所有项的最大偏移量,因为会有hash碰撞的情况,而weak_table_t采用了开放寻址来解决,所以某个entry实际存储的位置并不一定是hash函数计算出来的位置。mask和max_hash_displacement的作用后续会详细介绍。

    Runtime实现了weak_grow_maybeweak_compact_maybe这两个函数,用来在weak_table_t过满或者过空的情况下,及时调整其大小,以优化内存的使用率,提高运行效率。这两个函数通过调用weak_resize来调整weak_table_t大小。

    static void weak_grow_maybe(weak_table_t *weak_table)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        // Grow if at least 3/4 full.
        if (weak_table->num_entries >= old_size * 3 / 4) {
            weak_resize(weak_table, old_size ? old_size*2 : 64);
        }
    }
    
    • 该函数的目的是扩充HashTable的空间,扩充的条件是Table 3/4及以上的空间已经被使用。可以看出HashTable的初始化大小是64个weak_entry_t的空间,每次扩充后的空间都是当前空间的两倍,即2的N次方(N>=6)。
    static void weak_compact_maybe(weak_table_t *weak_table)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        // Shrink if larger than 1024 buckets and at most 1/16 full.
        if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
            weak_resize(weak_table, old_size / 8);
            // leaves new table no more than 1/2 full
        }
    }
    
    • 该函数的目的是缩小HashTable的空间,缩小的条件是HashTable目前的大小不小于1024个weak_entry_t的空间,并且低于1/16的空间被占用。缩小后的空间是当前空间的1/8。
    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;
        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
        
        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_entry_insert(weak_table, entry);
                }
            }
            free(old_entries);
        }
    }
    
    • 这个函数具体执行HashTable空间扩充和缩小,代码很容易看懂。首先根据new_size申请相应大小的内存,new_entries指针指向这块新申请的内存。设置weak_table的mask为new_size - 1。此处mask的作用是记录weak_table实际占用的内存边界,此外mask还有另一个重要的作用,之后会再次提及。
    • 众所周知,HashTable可能会有hash碰撞,而weak_table_t使用了开放寻址法来处理碰撞。如果发生碰撞的话,将寻找相邻(如果已经到最尾端的话,则从头开始)的下一个空位。max_hash_displacement记录当前weak_table最大的偏移值,即hash函数计算的位置和实际存储位置的最大偏差。

    众所周知,HashTable有一些标准的操作,比如插入,查询和删除等。weak_entry_insertweak_entry_for_referentweak_entry_remove分别对应这几种操作的实现。

    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_entry_t结构的entry(即对象和弱引用的映射关系)到weak_table中。hash_pointer是对引用(指针)的hash函数,具体的实现是对指针的地址,进行一系列的移位,异或等运算并返回。begin是hash_pointer的返回值和mask与运算的结果。上文提到的mask的第二个作用就是这里,之前提到,weak_table的size保持是2的N次方,而mask的值为size - 1,所以mask的二进制后N位都是1,而之前都是0,类似于00000111。所以与mask与操作后的值肯定会在[0,mask]这个区间内,也正好是weak_table实际的合法内存空间。
    • 可以看出,如果发生了hash碰撞的话,将会依次向下寻找空位,并且用max_hash_displacement来记录整个weak_table最大的偏移量。
    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];
    }
    
    • 该函数用于查询某个对象在weak_table中的位置,查询过程和插入过程基本类似。
    static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
    {
        // remove entry
        if (entry->out_of_line()) free(entry->referrers);
        bzero(entry, sizeof(*entry));
    
        weak_table->num_entries--;
    
        weak_compact_maybe(weak_table);
    }
    
    • 该函数的目的是从weak_table中删除某一项entry,即某个object和其weak引用的映射关系。函数的实现比较简单,首先将相应的内存清零,而后将weak_table的计数减一,然后调用weak_compact_maybe方法来判断是否需要缩小weak_table的空间。值得注意的是out_of_line()这个函数,与weak_entry_t的结构有密切的关系,后续的文章会继续讲解。

    以上是weak_table_t内部的实现。此外runtime提供了四个供外部调用的函数。值得注意的是,这些函数没有加锁,都不是线程安全的。

    id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                             id *referrer, bool crashIfDeallocating);
    
    void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
    
    bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
    
    void weak_clear_no_lock(weak_table_t *weak_table, id referent);
    

    四个函数的功能分别是,记录一对弱引用关系,删除一对弱引用关系,判断一个对象是否存在弱引用,和删除一个对象所有的弱引用。下面逐个分析每个函数的源码和实现。

    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;
    
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // ensure that the referenced object is viable
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            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("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;
            }
        }
    
        // now remember it and where it is being stored
        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;
    }
    
    • 函数首先处理了Tagged Pointer和object正在析构这两种特殊情况。Tagged Pointer是苹果在64位环境下为了节约内存使用,提升效率而引入的一种新的“指针”,具体的实现此处不表,有兴趣的读者可以自行查阅相关资料,后续的文章也有可能对此进行讲解。此外object正在dealloc时,也是不可以建立weak引用的映射关系的。
    • append_referrer的目的是对weak_entry_t类型的entry进行操作,添加一个Object的弱引用。后续讲解weak_entry_t的时候会再次提到。
    • 函数的整体逻辑比较简单,首先查找weak_table中有没有该object对应的entry,如果没有就添加并且记录weak引用,并且判断是否需要扩充HashTable,如果有的话就直接记录weak引用。
    bool
    weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
    }
    
    • Debug时判断当前weak_table中是否有对应某个object的entry。
    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))) {
            remove_referrer(entry, referrer);
            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);
            }
        }
    
        // Do not set *referrer = nil. objc_storeWeak() requires that the 
        // value not change.
    }
    
    • 该函数的整体逻辑也相对简单,首先找到object对应的entry,并且删除对应的weak引用,而后判断entry中的weak引用是否已经为空,如果是则从weak_table中将其删除。如何判断entry中weak引用是否为空,之后介绍weak_entry_t的文章会详细讲解,此处不表。
    void 
    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        objc_object *referent = (objc_object *)referent_id;
    
        weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
        if (entry == nil) {
            /// XXX shouldn't happen, but does with mismatched CF/objc
            //printf("XXX no entry for clear deallocating %p\n", referent);
            return;
        }
    
        // zero out references
        weak_referrer_t *referrers;
        size_t count;
        
        if (entry->out_of_line()) {
            referrers = entry->referrers;
            count = TABLE_SIZE(entry);
        } 
        else {
            referrers = entry->inline_referrers;
            count = WEAK_INLINE_COUNT;
        }
        
        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_entry_remove(weak_table, entry);
    }
    
    • 该函数的目的是删除一个object所有的weak引用,并且清理其空间。最后从weak_table中将其对应的entry删除。同样,清理weak_entry_t内存的部分此处不表,后续再进行讲解。

    至此,weak_table_t的基本结构和实现已经全部介绍完毕。后续会详细讲解weak_entry_t的结构和实现,以及NSObject如何和weak_table_t进行交互。这些加起来构成了完整的弱引用实现。

    相关文章

      网友评论

        本文标题:OC Runtime之Weak(1)---weak_table_

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