美文网首页
解读objc源码:weak的实现原理

解读objc源码:weak的实现原理

作者: ElaineYin | 来源:发表于2018-06-08 18:07 被阅读29次

    一、weak的作用

    这里的weak包括属性关键字weak和__weak两种:__weak用于修饰变量(variable),weak用于修饰属性(property)。

    1. weak是弱引用,用weak描述修饰或者所引用对象的计数不会加1
    2. weak修饰的指针在引用的对象被释放的时候自动被设置为nil,避免野指针
    3. weak可以解决使用block引起的循环引用。

    看到这里又想起了block和assign,复习一下这几个的区别:

    1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
    2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。assign用来修饰基本数据类型
    3.__block对象可以在block中被重新赋值,__weak不可以。
    4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

    OK,以上说的都没有错,大家也都会用,但是你知道是怎么实现的吗


    二、weak的源码实现

    1、先看下objc-weak.h文件里面的API
    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    struct weak_entry_t { ... };
    struct weak_table_t { ... };
    /// Adds an (object, weak pointer) pair to the weak table.
    id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                             id *referrer, bool crashIfDeallocating);
    /// Removes an (object, weak pointer) pair from the weak table.
    void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
    #if DEBUG
    /// Returns true if an object is weakly referenced somewhere.
    bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
    #endif
    /// Called on object destruction. Sets all remaining weak pointers to nil.
    void weak_clear_no_lock(weak_table_t *weak_table, id referent);
    

    看起来很简单的结构体和几个方法:

    • weak_table_t:翻译一下注释上的说明,弱引用的哈希表table,对象id作为key,weak_entry_t结构体作为value
    • weak_register_no_lock:向weak table里面添加一个object和它的weak引用指针
    • weak_unregister_no_lock:从weak table移除一个object和它的weak引用指针
    • weak_is_registered_no_lock:如果object有弱引用返回true
    • weak_clear_no_lock:object对象被销毁,把所有指向它的弱引用指针全部置为nil

    2、根据API看下.m里面的实现

    2.1 weak_register_no_lock添加弱引用
    源码实现:

    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;
        // ensure that the referenced object is viable
        。。。这里省略判断被引用的对象是否存在的代码,只看关键部分
        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);
        }
        return referent_id;
    }
    

    解释一下关键地方:

    1. if ((entry = weak_entry_for_referent(weak_table, referent))):判断weak_table是否存在这个对象referent
    2. 如果table表中已经存在对象referent,那么执行append_referrer(entry, referrer);把referrer这个新的引用加入到referent已经存在的引用列表中
    3. 如果不存在,执行
      weak_entry_t new_entry(referent, referrer):给对象referent创建一个新的引用列表
      weak_grow_maybe(weak_table):weak_table增加内存
      weak_entry_insert(weak_table, &new_entry):把referent的引用列表加入到weak_table中

    2.2 weak_unregister_no_lock:移除弱引用
    当我们使用weak指针指向一个对象__weak typeof(self) = self的时候,会调weak_register_no_lock在weak table表中添加这个引用,但是移除引用什么时候会用呢,还是看一下源码和注释:

    /** 
     * Unregister an already-registered weak reference.
     * This is used when referrer's storage is about to go away, but referent
     * isn't dead yet. (Otherwise, zeroing referrer later would be a
     * bad memory access.)
     * Does nothing if referent/referrer is not a currently active weak reference.
     * Does not zero referrer.
     */
    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        // referent是对象也就是weak table中的key
        objc_object *referent = (objc_object *)referent_id;
        //*referrer是指向这个对象的弱引用指针
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
    
        if (!referent) return;
        ///从weak_table中根据key(referent)找到entry(指向对象弱引用列表的指针)
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            /// 从引用列表中移除这个引用referrer, remove_referrer这个方法会把这个referrer置为nil
            remove_referrer(entry, referrer);
            /// 之后遍历对象referent的引用列表是不是空了
            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) {
                /// 如果该对象的弱引用列表已经empty,就把该对象从weak_table中移除
                weak_entry_remove(weak_table, entry);
            }
        }
    }
    

    这个应该是这种情况下使用的,比如我们在一个方法里面这样定义:

    - (void)testWeakReferrer {
        __weak typeof(self) weakSelf = self;
        ...
        return;
    }
    

    weakSelf是方法的局部变量,当方法执行完的时候,局部变量weakSelf会被销毁,这个时候系统应该就会自动执行weak_unregister_no_lock方法,把weakSelf从self对象的弱引用列表中移除,并且把weakSelf置为nil。


    2.3 weak_clear_no_lock清空所有的weak指针

    /** 
     * Called by dealloc; nils out all weak pointers that point to the 
     * provided object so that they can no longer be used.
     * 
     * @param weak_table 
     * @param referent The object being deallocated. 
     */
    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);
        ...
        // zero out references
        weak_referrer_t *referrers;
        size_t count;
        ...//获取列表中的数量
        for (size_t i = 0; i < count; ++i) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                if (*referrer == referent) {
                    *referrer = nil;
                }
                ...
            }
        }
        
        weak_entry_remove(weak_table, entry);
    }
    

    Called by dealloc,当对象被销毁的时候调用这个方法,在这个方法里面把所有指向这个对象的弱引用指针全部置为nil,最后从weak table中移除该对象和它的弱引用列表。

    ok,这也就解释了为什么weak指针会被自动置为nil了

    相关文章

      网友评论

          本文标题:解读objc源码:weak的实现原理

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