weak原理探究

865 阅读7分钟

前言

weak为ios的一个修饰符,修饰的变量具备如下特征,不会对指向对象进行retain(即引用计数+1),对象释放时weak会指向nil,调用方法不会崩溃

探究weak之前先看看怎么找到的weak怎么调用的方法吧

首先创建一个obj指向,NSObject类型的对象

NSObject *objc = [NSObject alloc];
__weak id obj = objc;

然后打开xcode的debug的workflow为Always Show Disassembly

这样打断点到weak那一行,即可了解到weak初始化变量时,经历了哪些方法

紧接着会发现,调用了objc_initWeak方法来初始化weak修饰的变量

下面来探究下objc_initWeak

objc_initWeak

objc-8.8.2中搜索objc_initWeak方法,即可搜索到下面源码实现,我们来看看它做了什么

//*location为weak修饰的变量的指针地址,newObj为实际引用的对象
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
	//继续调用storeWeak
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

storeWeak

在objc_initWeak之后调用了storeWeak方法

其主要逻辑为从哈希表中移除老的引用,加入新的引用,可以直接定位到与weak相关,从weak_unregister_no_lock方法开始查看

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;

    // 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:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

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

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    //如果存在旧的引用,则在weak_table中移除旧的引用
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    //存在新的引用则在哈希表中加入新的引用
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            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);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

weak_unregister_no_lock

当存在旧的引用是时,会调用weak_unregister_no_lock会进入到哈希表中,筛选移除掉旧的引用值,首先找到对象在weakTable中以referent为key获取到的entry,调用remove_referrer方法,从entry中删除弱旧的引用关系

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
	//拿出weak实际引用的对象
    objc_object *referent = (objc_object *)referent_id;
    //拿出weak指针修饰的对象
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return; //引用对象已经销毁结束

	//从弱引用表中找到referent所在的入口entry,如果存在则调用remove_referrer进行移除操作
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    	//删除旧的引用关系
        remove_referrer(entry, referrer);
        查看该entry是否引用为空
        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);
        }
    }
}

//删除旧的引用
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }

    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    //从entry中遍历找到旧的引用old_referrer,如果找一圈找不到直接结束
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    //找到了旧的易用对象所在索引直接置空,整体数量-1
    entry->referrers[index] = nil;
    entry->num_refs--;
}

介绍完毕后通过weak_unregister_no_lock方法,移除旧的弱引用关系逻辑后,开始通过weak_register_no_lock方法加入新的弱引用逻辑

weak_register_no_lock

weak_register_no_lock为注册新的弱引用关系,首先通过实际引用的对象referent_id,从weak_table中找到对应的entry,然后直接追加进入,否则创建新的entry,插入新的引用

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
            if (deallocatingOptions == 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
    //首先通过referent为key,从weak_table中找到该引用对象在弱引用表对应的entry
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    	//已经存在该entry,则直接追加新的亦可能用
        append_referrer(entry, referrer);
    } 
    else {
    	//添加新的entry
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table); //根据需要扩展weak_table
        //加入新的引用
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}

append_referrer

append_referrer为向entry中添加新的弱引用关系

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
	//尝试将新的引用new_referrer方法到inline_referrers中
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        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());

	//查看是否需要扩展entry,如果需要扩展并插入
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    //找到为nil的索引位置,放入新的引用,然后数量+1
    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_insert

在weak_register_no_lock中,当entry不存在的时候开始创建entry,并调用weak_entry_insert方法开始插入新的引用

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
	//获取weak_entries
    weak_entry_t *weak_entries = weak_table->weak_entries;
    ASSERT(weak_entries != nil);

	与append_referrer相似,找到为nil的索引位置,放入新的引用,然后数量+1
    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的创建方法就到此位置了,下面介绍weak引用的销毁

weak_clear_no_lock

当weak所指向的对象销毁时,会走起dealloc方法,然后rootDealloc,在object_dispose,在objc_destructInstance,在clearDeallocating,在sidetable_clearDeallocating,再sidetable_clearDeallocating,最后会走到weak_clear_no_lock方法里面

weak_clear_no_lock会从weak_table中通过referent_id为key,获取到entry,从中移除引用关系,源码实现如下

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
	//弱引用对象
    objc_object *referent = (objc_object *)referent_id;
	
    //找到该对象对应的entry
    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;
    
    从entry中获取referrers的集合
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    //将referrers集合中的指向即将销毁的该对象的指针指向为空,这样在使用weak修饰的变量,就相当于用nil调用方法发送消息
    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_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();
            }
        }
    }
    
    //如果 entry中的referrers不存在移除,根据是否为空销毁掉空集合
    weak_entry_remove(weak_table, entry);
}

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line()) free(entry->referrers); //销毁referrers引用
    bzero(entry, sizeof(*entry));

    weak_table->num_entries--; //entry数量减少1

    weak_compact_maybe(weak_table); //根据需要精简weak_table
}

至此weak的的创建和销毁介绍的差不多了

总结

创建一个weak对象

经过如下步骤:

先从SideTables表中找到弱引用表weak_table,weak_table也为散列表

从weak_table中,通过referent找到或者创建weak_entry,weak_entry为弱引用结构

如果存在老的引用关系(即:weak指针指向了另一个对象),需要到过去的对象中移除对该weak的引用,避免老的对象销毁时,将新的weak指针置为空,操作出现异常问题

如果weak_entry存在则通过append_referrer方法将新的弱引用追加进去,否则通过被引用对象referent创建新的entry,加入到weak_table中,然后通过weak_entry_insert插入新的引用

当被引用对象referent被销毁时

经过如下步骤:

通过dealloc中,找到weak_table

通过当前对象找到其所在的entry

将entry其里面的引用关系删除,并将referrers(即weak引用该对象的指针集合),全部指向nil

然后从weak_table中移除entry,销毁entry

注意

此时对象已经销毁,weak所修饰的对象也不是指向已经销毁对象,而是指向nil,因此通过此nil调用方法不会崩溃

而weak所修饰的对象的生命周期,跟持有者有关系,其可能为一个临时变量,随着该栈pop被释放,可能为属性,随着持有者释放,也跟随释放