iOS-weak原理

weakiOS开发中很常见的知识点,大家都知道weak是一个修饰词,作用是对修饰的对象弱引用,在对象被释放的时候引用计数会置空。

weak创建流程

main函数写入以下代码,设置一个断点,然后进入汇编

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id __weak objc = object;
    }
    return 0;
}
image

然后我们设置一个符号断点,objc_initWeak,会进入以下代码:

id objc_initWeak(id *location, id newObj)
{
    // 判断所引用的对象是否存在
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak
        (location, (objc_object*)newObj);
}

这个方法有两个参数:

  • location:指__weak指针的地址,它是一个指针的地址。之所以要存储指针的地址,是因为当我们释放对象的时候需要将__weak指针指向的内容置为nil
  • newObj:所引用的对象。
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    ......
 retry:
    if (haveOld) {
        // 如果已经存在引用
        // 给oldObj赋值原来的指针地址
        // 然后根据oldObj取出之前的散列表oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        // 如果不存在之前的引用 则将oldTable置为nil
        oldTable = nil;
    }
    if (haveNew) {
        // 根据newObj取出散列表newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // 加锁
    SideTable::lockTwo(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(oldTable, newTable);
        goto retry;
    }

    if (haveNew  &&  newObj) {
        // 有新对象而且可以存新的weak引用
        // 判断新对象的类是否已经初始化
        // 没有初始化就将其初始化
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        // 如果有旧的引用,调用weak_unregister_no_lock清除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        // 有新的引用需要设置,调用weak_register_no_lock注册引用
        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
    
        // Set is-weakly-referenced bit in refcount table.
        // 设置isa指针的weakly_referenced标志位
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // 将新值放入location的地址位
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo(oldTable, newTable);

    return (id)newObj;
}
  • HaveOldweak指针之前是否指向了一个弱引用
  • HaveNewweak指针是否指向一个新的弱引用

该方法主要是在存储weak指针的时候,判断之前是否有过弱引用,有的话调用weak_unregister_no_lock方法清除;然后在判断是否要存储新的弱引用,需要的话,调用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;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    ......

    // now remember it and where it is being stored
    weak_entry_t *entry;
    // 从weak散列表找当前对象的引用数组
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 找到了将新的引用直接拼接到表中
        append_referrer(entry, referrer);
    } 
    else {
        // 没找当前对象的引用数组 创建了这个数组 - 插入weak_table
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    return referent_id;
}

上述方法注册了一个新的对象和weak指针,如果没有的话就会创建一个新的weak object entry。参数如下:

  • weak_table_t *weak_table:存储所有weak引用的散列表
  • id referent_id: 需要引用的对象指针
  • id *referrer_id: 弱引用指针的地址
  • bool crashIfDeallocating:如果引用正在释放的时候是否会崩溃

该方法中引入了两个非常关键的东西:

  • weak_table_t: 存储所有weak引用的散列表
  • weak_entry_t: 存储每一个weak引用相关信息结构的数组
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

weak_register_no_lock主要实现的功能是如果对象可以被弱引用,则将被弱引用对象所在的weak_table中的weak_entry_t哈希数组中取出对应的weak_entry_t,然后将对象和弱引用指针插入weak_entry_t;如果weak_entry_t不存在,则会新建一个weak_entry_t数组,然后将将对象和弱引用指针插入创建的weak_entry_t数组,再把weak_entry_t数组插入weak_table表中。

插入弱引用值

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // 先往`inline`数组里插入
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // 如果不能插入inline数组,初始化一个新数组,将inline数组里面的元素插入新数组
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        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++;
}

参数:

  • weak_entry_t *entry:存储当前对象所有weak pointers的集合
  • objc_object **new_referrer:需要插入的新引用指针

该方法将给定的引用往weak_entry_tweak指针集合里面插入,先往inline里存放,如果inline存放不下,则会创建一个新的数组referrers,然后inline里的元素插入referrers,当referrers容量不够时,会对其进行扩容操作,数组里会自动去重。

反注册弱引用

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))) {   
        // 如果从weak_table表中获取到引用数组
        // 删除原来的引用
        // 如果引用数组里没有数据,则将引用数组从weak_table里移除
        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);
        }
    }
}

该方法用来反注册一个已经注册的weak引用,也就是清除注册。

  • weak_table_t *weak_table:存储weak的散列表
  • id referent_id:引用的对象
  • id *referrer_idweak引用的地址

weak释放

跳出作用域的时候,弱引用就会释放对象,那么它是怎么释放对象的呢?下面我们来查看一下流程:

- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

// 清除weak表中的相关引用
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    
    // 通过引用对象从weak_table里找到对应的weak_entry_t
    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;
    
    // 从inline_referrers和referrers查找对应的引用指针
    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) {
            // 将引用指针置为nil
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

通过代码,我们可以看出,当跳出作用域的时候,先从在散列表中找到存储弱引用的散列表weak_table,通过引用指针找到引用对象,然后再通过引用对象从weak_table里找到对应的weak_entry_t

  • 先在weak_entry_treferrers里面查找,找到就将对应的引用指针地址置为nil
  • 找不到就在inline_referrers里查找,然后将对应的引用指针地址置为nil

总结:

创建弱引用的过程:

    1. 判断当前对象是否存在弱引用,是否需要添加新的弱引用
    1. 通过SideTable找到存储弱引用的weak_table
    1. 存在弱引用,对其执行反注册,清除操作
    1. 需要添加新的弱引用,则根据referent弱引用对象,找weak_entry_t
    • 找到,将弱引用指针插入weak_entry_tappend_referrer(entry, referrer)
    • 找不到,新建一个weak_entry_t,写入弱引用指针,然后把新建的weak_entry_t插⼊到weak_table
image

释放weak

当出了作用域的时候,会将作用域内弱引用的对象进行释放,释放的关键点在于将通过引用对象找到的弱引用表weak_table中的引用指针置为nil、将引用指针地址置空。

image

你可能感兴趣的:(iOS-weak原理)