OC底层探索 - Weak的实现

1,254 阅读3分钟

1. Weak的简介

在iOS开发中,经常会使用weak关键字解决由于对象之前互相强引用导致内存无法释放内存泄漏的问题,使用weak关键字引用计数不会加1,并且在引用对象释放会置为nil,也避免了野指针访问坏内存导致崩溃的状况。

拓展:为什么修饰代理使用weak而不是用assign

assign可用来修饰基本数据类型,也可修饰OC的对象,但如果用 assign修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为nil,否则会产生野指针,如果还通过此指针操作那块内存,会导致EXC_BAD_ACCESS错误,调用了已经被释放的内存空间;而weak只能用来修饰OC对象,而且相比assign比较安全,如果指向的对象消失了,那么它会自动置为nil,不会导致野指针。

2. Weak的工作流程

  1. 通过SideTable找到我们的weak_table
  2. weak_table根据referent找到或者创建weak_entry_t
  3. 然后append_referrer(entry, referrer)将新的弱引用对象加进去entry
  4. 最后weak_entry_insert把entry加到weak_table

3. Weak的底层探索

3.1 objc_initweak

通过xcode打开debug模式,可以看到汇编代码如下

0x100003c14 <+88>:  bl     0x100003cc8               ; symbol stub for: objc_initWeak
0x100003c1c <+96>:  bl     0x100003cbc               ; symbol stub for: objc_destroyWeak

通过runtime源码查看objc_initWeak的实现如下,可以看到是weak的入口函数,然后就走到storeWeak

/** 
 * 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;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>

        (location, (objc_object*)newObj);
}

3.2 storeWeak

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;

    /*省略中间代码*/
    if (haveOld) {//存在旧值,通过hash查找,擦除旧值
        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.
        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;

}

3.3 weak_register_no_lock

弱引用表weak_table 先判断析构

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
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;
    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);
    }
    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.
    return referent_id;
}

3.3 weak_entry

在弱引用表中(也是哈希表)通过散列函数,获取实体 再通过函数append_referrer将对象new_referrer加到weak_entryentry->referrers数组当中

3.4 dealloc

如何实现weak弱应用对象置为nil 通过 sidetable_clearDeallocating

/** 

 * 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;
    //关键代码,通过之前的散列表,找到弱应用表,在通过referent在weak_table找到对应的实体,然后遍历删除
    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;
    
    //for循环,删除referent与referrer相同的
    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);
}