美文网首页
iOS weak的实现原理

iOS weak的实现原理

作者: 赵哥窟 | 来源:发表于2018-11-05 17:29 被阅读25次
    1.weak简述

    weak表其实是一个hash表,Key是所指对象的地址,Value是weak指针的地址数组,weak是弱引用,所引用对象的计数器不会+1,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。

    2.weak底层原理
    2.1weak 的实现原理可以概括一下三步:

    1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

    2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

    3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    初始化weak表方法
    //初始化weak表
    /** 
     * Initialize a fresh weak pointer to some object location. 
     * It would be used for code like: 
     *
     * (The nil case) 
     * __weak id weakPtr;
     * (The non-nil case) 
     * NSObject *o = ...;
     * __weak id weakPtr = o;
     * 
     * @param addr Address of __weak ptr. 
     * @param val Object ptr. 
     */
    id objc_initWeak(id *addr, id val)
    {
        *addr = 0;
        if (!val) return nil;
        return objc_storeWeak(addr, val); // 存储weak对象
    }
    
    添加引用时
    /** 
     * This function stores a new value into a __weak variable. It would
     * be used anywhere a __weak variable is the target of an assignment.
     * 
     * @param location The address of the weak pointer itself
     * @param newObj The new object this weak ptr should now point to
     * 
     * @return \e newObj
     */
    id
    objc_storeWeak(id *location, id newObj)
    {
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
        spinlock_t *lock1;
    #if SIDE_TABLE_STRIPE > 1
        spinlock_t *lock2;
    #endif
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
        // Retry if the old value changes underneath us.
     retry:
        oldObj = *location;
        
        oldTable = SideTable::tableForPointer(oldObj);
        newTable = SideTable::tableForPointer(newObj);
        
        lock1 = &newTable->slock;
    #if SIDE_TABLE_STRIPE > 1
        lock2 = &oldTable->slock;
        if (lock1 > lock2) {
            spinlock_t *temp = lock1;
            lock1 = lock2;
            lock2 = temp;
        }
        if (lock1 != lock2) spinlock_lock(lock2);
    #endif
        spinlock_lock(lock1);
    
        if (*location != oldObj) {
            spinlock_unlock(lock1);
    #if SIDE_TABLE_STRIPE > 1
            if (lock1 != lock2) spinlock_unlock(lock2);
    #endif
            goto retry;
        }
    
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
        // weak_register_no_lock returns nil if weak store should be rejected
    
        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
    
        // Do not set *location anywhere else. That would introduce a race.
        *location = newObj;
        
        spinlock_unlock(lock1);
    #if SIDE_TABLE_STRIPE > 1
        if (lock1 != lock2) spinlock_unlock(lock2);
    #endif
    
        return newObj;
    }
    

    旧对象解除注册操作 weak_unregister_no_lock
    该方法主要作用是将旧对象在 weak_table 中接触 weak 指针的对应绑定。根据函数名,称之为解除注册操作。从源码中,可以知道其功能就是从 weak_table 中接触 weak 指针的绑定。而其中的遍历查询,就是针对于 weak_entry 中的多张弱引用散列表。

    新对象添加注册操作 weak_register_no_lock
    这一步与上一步相反,通过 weak_register_no_lock 函数把心的对象进行注册操作,完成与对应的弱引用表进行绑定操作。

    初始化弱引用对象流程一览
    image.png
    weak释放为nil过程

    1、调用objc_release
    2、因为对象的引用计数为0,所以执行dealloc
    3、在dealloc中,调用了_objc_rootDealloc函数
    4、在_objc_rootDealloc中,调用了object_dispose函数
    5、调用objc_destructInstance
    6、最后调用objc_clear_deallocating。
    对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    在对象被释放的流程中,需要对objc_clear_deallocating方法进行深入的分析

    void objc_clear_deallocating(id obj) 
    {
        assert(obj);
        assert(!UseGC);
        if (obj->isTaggedPointer()) return;
        obj->clearDeallocating();
    }
    
    //执行 clearDeallocating方法
    inline void objc_object::clearDeallocating()
    {
        sidetable_clearDeallocating();
    }
    // 执行sidetable_clearDeallocating,找到weak表中的value值
    void  objc_object::sidetable_clearDeallocating()
    {
        SideTable *table = SideTable::tableForPointer(this);
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        spinlock_lock(&table->slock);
        RefcountMap::iterator it = table->refcnts.find(this);
        if (it != table->refcnts.end()) {
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                weak_clear_no_lock(&table->weak_table, (id)this);
            }
            table->refcnts.erase(it);
        }
        spinlock_unlock(&table->slock);
    }
    

    对weak置nil的操作最终调用执行weak_clear_no_lock方法用于执行置nil的操作。执行方法如下:

    /** 
     * 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);
        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);
    }
    
    

    objc_clear_deallocating该函数的动作如下:

    1、从weak表中获取废弃对象的地址为键值的记录
    2、将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
    3、将weak表中该记录删除
    4、从引用计数表中删除废弃对象的地址为键值的记录

    相关文章

      网友评论

          本文标题:iOS weak的实现原理

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