美文网首页iOSios开发那些事
iOS 性能优化-weak存储原理

iOS 性能优化-weak存储原理

作者: 淡定的笨鸟 | 来源:发表于2019-05-05 14:18 被阅读237次

在日常开发过程中,经常会出现循环引用而导致的内存泄露的问题,比如我们有a,b两个对象,对象中都有两个属性name和age,然后出现了下列情况

a.name = b.name;
b.age = a.age;

  • a对象通过a.name引用了b对象,所以b的引用计数为1
  • b对象通过b.age引用了a对象,所以a的引用计数也为1

这时a执行完任务后,发现引用计数还是1,不能释放。b执行完任务后,发现引用计数还是1,也不能释放。
我们看到它们彼此都需要对方,然后boom!循环引用。

这时候我们可以加一个__weak修饰,

__weak a.name = b.name;
b.age = a.age;

  • a对象通过__weak a.name弱引用了b对象,所以b的引用计数为0。
  • b对象通过b.age引用了a对象,所以a的引用计数也为1。

这时,a执行完任务后,发现引用计数还是1,不能释放。b在执行完任务后引用计数变为0,b对象被释放,从而a的引用计数减1变成0,也被释放。

那么weak在这里面是如何执行的呢?下面开始我们的正题。


weak存储原理.png

上图是weak修饰对象时系统底层的流程,下面结合runtime源码梳理一下。
1、objc_initWeak初始化

//location是弱引用对象的内存地址
id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

2、调用storeWeak函数存储weak弱引用指针
直接看代码

static id 
storeWeak(id *location, objc_object *newObj)
{
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    //如果之前存储过这个弱引用对象
    if (haveOld) {
        //则把原来弱引用对象从weak_table中移除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    //如果这是新的弱引用对象
    if (haveNew) {
        //把这个新的弱引用对象添加到weak_table中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
        //判断这个对象是否属于TaggedPointer类型的,如果属于TaggedPointer类型那就厉害了,根本不需要有引用计数一说
        //taggedPointer对象是为了苹果为了性能最大化做的处理,针对不需要到栈和堆中寻找的对象,可以直接从地址中通过一定的算法得到他们的值。
        if (newObj  &&  !newObj->isTaggedPointer()) {
            //标记该对象是弱引用对象
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    //给这两个表解锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

那这里面有两个重要的函数weak_unregister_no_lockweak_register_no_lock
1)首先判断弱引用指针表中是否有当前要存储的weak弱引用指针,看代码

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;
    //调用weak_entry_for_referent找到entry弱引用指针条目
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //从内层inline_referrers中移除entry
        //inline_referrers中只能存储4个弱引用指针,多了就要存储到referrers中,所以要多一步empty判空操作
        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;
                }
            }
        }

        //从weak_table中移除entry弱引用条目
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

2)调用weak_register_no_lock存储entry弱引用指针条目,看代码

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;//弱引用变量的地址

    //如果该弱引用对象是taggedPointer对象,则不做处理直接返回该对象
    //taggedPointer对象是为了苹果为了性能最大化做的处理,针对不需要到栈和堆中寻找的对象,可以直接从地址中通过一定的算法得到他们的值。
    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;//弱引用指针的条目
    //判断weak_table中是否有该条目
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //如果有,则把弱引用对象追加进去
        append_referrer(entry, referrer);
    } 
    else {
        //如果没有,则创建一个
        weak_entry_t new_entry(referent, referrer);
        //如果索引已经超过原来的3/4,则给weak_table扩容
        weak_grow_maybe(weak_table);
        //将新的entry插入weak_table
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

综上,就是系统在使用weak的存储原理,当然也可以继续深入的探索,日后会抽时间继续探索哒,希望能和大家共同进步。
参考博客:
iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-一
Objective-C 小记(10)__weak
深入浅出ARC(上)

相关文章

网友评论

    本文标题:iOS 性能优化-weak存储原理

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