老青菜

iOS weak 底层分析

2019-12-15

weak,弱引用,不会增加对象的引用计数,对象被释放的时候,指针自动被设置为nil,避免了野指针和循环引用的问题,使用如下:

NSObject *obj = [[NSObject alloc] init];
__weak NSObject *obj1 = obj;

原理

对于weakobjc维护了一个hash表,key是修饰的对象的地址,valueweak指针的地址数组。

一个weak指针的生命周期大致分为3块:

  1. objc_initWeak(id *location, id newObj),入口函数,传入weak ptrweak obj
  2. storeWeak(id *location, objc_object *newObj)更新locationweak ptr)的弱引用表。
  3. 对象dealloc销毁的时候,调用clearDeallocating清除this的弱引用表。

接下来我们来看下objc源码:

objc_initWeak

找到NSObject.mmobjc_initWeak的源码:

/** 
 * 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.
 */
 // location:weak 指针
 // newObj:weak 对象
id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
    //更新weak指针
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

从注释来看,objc_initWeak主要是初始化weak指针,调用了storeWeak

  1. locaiton,代表weak ptr,即weak指针。
  2. newObjweak指针修饰的新对象。

我们继续往下看。

storeWeak

找到NSObject.mmNSObject.mm的源码:

// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
//   that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
//   If CrashIfDeallocating is false, nil is stored instead.
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
 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;
    }
    // 把location从旧对象weak_table中清除
    // Clean up old value, if any.
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // 把location保存到新对象的weak_table中
    // Assign new value, if any.
    if (HaveNew) {
        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
        // 标记新对象是weak引用
        // 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;
}

StoreWeak函数主要是更新location修饰的对象的弱引用表,包含新旧弱引用表的更新,这里有两个逻辑

  1. 如果HaveOld = true,即之前location有指向其他对象,这里需要先清除之前对象的weak_table里的location
  2. 如果HaveNew = true,即现在location指向新的对象newObj,这里需要把location添加到新对象newObjweak_table里。

dealloc

对象释放后,是如何更新SideTables的呢?打开objc源码:

//NSObject.mm
- (void)dealloc {
    _objc_rootDealloc(self);
}

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

object_dispose()

id object_dispose(id obj) {
    if (!obj) return nil;
    objc_destructInstance(obj);
    free(obj);
    return nil;
}

void *objc_destructInstance(id obj)  {
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = !UseGC && obj->hasAssociatedObjects();
        bool dealloc = !UseGC;
        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        if (dealloc) obj->clearDeallocating();
    }

    return obj;
}

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

/// 清除对象的弱引用记录
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
    assert(isa.indexed  &&  (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();
}

调用层次有点多,总结一下,dealloc包括以下几个步骤:

  1. 释放c++实例变量(ivars),调用析构函数。
  2. 移除Associated关联对象(在category中关联的变量)。
  3. 释放对象的弱引用表。

底层结构

SideTables

SideTables一个全局的hash数组,里面存储了SideTable类型的数据,在iOS端上其长度为64

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
class StripedMap {
    enum { CacheLineSize = 64 };
#if TARGET_OS_EMBEDDED
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
}

SideTable

struct SideTable {
    // 自旋锁
    spinlock_t slock;
    // obj对象的引用计数
    RefcountMap refcnts;
    // weak弱引用hash表
    weak_table_t weak_table;
    //...
};

SideTable保存了weak弱引用的信息和对象的引用计数。

  1. refcnts,obj对象的引用计数,以对象地址为key,value是引用计数。
  2. weak_table_t,存储weak弱引用hash表。

这里需要注意的,在手机端上,SideTables一共有64个节点,所以一个SideTable会对应多个对象,也就是一个weak_table会存储多个对象的弱引用指针。

weak_table_t

/// 全局弱引用表,以obj为key,weak_table_t为value
/// 一个weak_table_t对应多个obj对象
struct weak_table_t {
    // hash数组
    weak_entry_t *weak_entries;
    // hash 元素个数
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

/// 根据obj对象,获取对应的 weak_entry_t 弱引用信息。
/// 一个weak_table_t对应多个obj对象
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) {
    assert(referent);
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil;
    size_t index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}

weak_table_t存储了多个对象的弱引用信息,即弱引用数组,主要包含以下成员:

  1. weak_entries,动态的弱引用数组。
  2. num_entries,弱引用数量。
  3. weak_entry_for_referent函数,根据对象,获取弱引用信息weak_entry_t,在weak_register_no_lockweak_unregister_no_lock清除、添加weak ptr中会用到。

weak_entry_t

/// 存储对象和对象的弱引用指针信息
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    // weak修饰的对象
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // weak 弱引用指针数组
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            // weak 弱引用指针数量
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

weak_entry_tweak_table_t结构差不多,但是weak_entry_t对应到具体某个对象的弱引用hash表,主要包含以下成员:

  1. referent,weak修饰的对象。
  2. referrers,对象的弱引用指针数组。
  3. num_refs,对象的弱引用指针数量。

到这里,weak的底层结构大致都说完了,最后我们画一张图来总结一下:

参考链接

opensource objc4

Tags: objc
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章