weak 底层结构

weak 修饰词在解决循环引用问题的时候经常使用,weak 为什么可以解决循环引用问题呢?它的底层原理是什么?我们今天就来探讨一下。

// 例 1
NSObject *obj = [[NSObject alloc] init];
__weak id weakObj = obj;

通过上面的两行代码,我们创建了一个 weak 类型的对象 weakObj 指向对象 obj。打上断点,通过汇编我们可以发现底层调用了 objc_initWeak

objc_initWeak 方法

下载 objc4 源码,可以找到 objc_initWeak 的源码如下:

id
objc_initWeak(id *location, id newObj)
{
// 此时的 *location 为 weakObj 指针
// newObj 为 obj对象
if (!newObj) {
*location = nil;
return nil;
}

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

storeWeak 方法

objc_initWeak 底层调用的是 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;

retry:
if (haveOld) {
// 如果 weakObjc 之前有引用过其它对象,通过 weakObj 对象指针查找旧 SideTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
// 通过 obj 查找对应的 SideTable
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}

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

// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}

if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 如果 cls 没有初始化,需要先初始化
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);

previouslyInitializedClass = cls; // 初始化后赋值,防止再次进入判断

goto retry; // 回到开头,重新获取 newObj
}
}

// 有旧值,清除旧值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// Assign new value, if any.
if (haveNew) {
// 调用 weak_register_no_lock 方法,将 weakObj 的指针地址存储到 newObj 对应的 weak_entry_t 中
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);

// 更新 newObj 中 isa 的 bitsweakly_referenced 标志位
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}

把 weakObj 指针指向 newObj
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}

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

return (id)newObj;
}

流程如下:

weak 存储结构

SideTables()方法定义如下

static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}

此方法返回的是一个 StripedMap<SideTable>& 类型的值

// StripedMap 类,省略无关代码
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
// PaddedT 为泛型,StripeCount 为 8
PaddedT array[StripeCount];

// 根据指针地址进行哈希计算,找到数组下标
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // 哈希函数
}

public:
// 重载 [] 操作符,通过指针地址来获取数组对应的值
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}

};
  1. StripedMap 是一个哈希表,array属性存储有 8 张 SideTable
  2. 通过重载 [] 操作符,方便通过指针获取存储在哪个 SideTable
struct SideTable {
spinlock_t slock; // 锁
RefcountMap refcnts; // 引用计数表
weak_table_t weak_table; // weak 表
};
  1. slock: 自旋锁,操作 SideTable 时进行加锁
  2. refcnts: 引用计数 hash 表,存储对象的引用计数
  3. weak_table: 弱引用 hash 表,存储对象的弱引用指针
struct weak_table_t {
weak_entry_t *weak_entries; // hash数组,用来存储弱引用对象的相关信息
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
  1. weak_entries: hash 数组,一个对象的弱引用信息就是一个 weak_entry_t,查询时,通过 obj 对象的指针地址在数组中查询到对应的weak_entry_t。
  2. num_entries: 数组中元素的个数
  3. mask: 数组长度 - 1,会参与哈希函数的计算。
  4. max_hash_displacement: 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过该值)
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
  1. referrers: 被弱引用的对象,例子中的 obj 对象。
  2. union: 联合体,内部有定长数组 inline_referrers[WEAK_INLINE_COUNT] 和动态数组 weak_referrer_t *referrers,当弱引用该对象的指针数少于 WEAK_INLINE_COUNT 时使用定长数组,超过后,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。

结构如下图所示:

弱引用表的结构很清晰了,他是一个 hash 表,Key 是所指对象的地址,Value 是 weak 指针的地址数组

添加弱引用流程

weak_register_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; weak 指针强转为指针的指针


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

// 确定对象没有正在进行析构
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(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;
}
}

weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 如果通过弱引用对象在 weak_table 中查找到了 weak_entry_t,直接把 weak 指针添加进 weak 指针数组即可
append_referrer(entry, referrer);
}
else {
// 没有找到,则新创建一个 weak_entry_t 后再存储 weak 指针
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}

return referent_id;
}

调用时传参是 weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);,我们来对这些参数进行一下说明:

  1. weak_table: 弱引用表,通过 SideTable 的 weak_table 可以得到此值。
  2. referent_id: 被弱引用的对象,例 1 中的 obj 对象。referent_id 被赋值给 *referent 指针变量。
  3. *referrer_id: weak 指针,例 1 中 weakObj 对象的指针。*referrer_id 强转为指针的指针后赋值给 **referrer。指针的指针说明可参考 C++ 指向指针的指针
  4. crashIfDeallocating: 如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。

整体流程如下:

  1. 判断 referent 是否为 nil,或者是 TaggedPointer,是则直接返回。
  2. 判断 referent 是否正在析构,正在析构则抛出异常。
  3. 调用 weak_entry_for_referent 方法,根据 referent 地址从 weak_table 中查找对应的 weak_entry,如果能找到,则调用append_referrer 插入 weakObj 指针的指针。找不到则新创建一个 weak_entry_t,在调用 weak_entry_insert 插入。

weak_entry_for_referent 查找 weak_entry_t

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;

// 根据 referent 地址和 mask 进行位运算计算出初始数组下标,因为有哈希冲突的存在,所以还需要进行循环判断。
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin; // 通过更改 index 解决哈希冲突
size_t hash_displacement = 0; // 记录哈希冲突次数

// 能进入循环表示 index 位置不是我们需要找的 weak_entry
while (weak_table->weak_entries[index].referent != referent) {

// (index + 1) & mask 重新计算 index 下标
index = (index+1) & weak_table->mask;

// 如果 index == begin 则表示 weak_table 出错了
if (index == begin) bad_weak_table(weak_table->weak_entries);

hash_displacement++; // 冲突次数加 1

// 冲突次数大于 weak_entries 数组长度,则表示没找到,返回 nil
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}

return &weak_table->weak_entries[index];
}

append_referrer 添加弱引用

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
// 如果没有超过定长数组最大值
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;
}
}

// 遍历完定长数组,发现都存满了,需要重新移动到动态数组存储
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());

// 如果存储数量超过了 3/4,扩容一倍之后在插入
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_insert 插入弱引用

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

// 根据 referent 地址和 mask 进行位运算计算出初始下标
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0; // 记录哈希冲突次数

// 如果 index 下标处已经有值了,则表示哈希冲突了,需要重新计算位置
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}

// new_entry 指针存入数组
weak_entries[index] = *new_entry;
// 数组个数 + 1
weak_table->num_entries++;

// 如果冲突次数大于 max_hash_displacement,更新 max_hash_displacement
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}

移除销毁弱引用流程

weak_unregister_no_lock

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;

// 查询对应的 entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {
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;
}
}
}

// 如果 entry 的 referrers 元素都移除了,从 weak_table 中移除 entry
if (empty) {
weak_entry_remove(weak_table, entry);
}
}

// 没有找到 entry 则不做任何处理
}

remove_referrer

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;
}

// 动态数组,通过哈希计算找到对应的 weak 指针地址,置为 nil
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
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;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}

dealloc 对象销毁时如何处理弱引用?

对象引用计数为 0 时,底层会调用 rootDealloc 方法,进行销毁操作。

inline void
objc_object::rootDealloc()
{
// 根据 isa 标识位判断是否有弱引用,关联对象,自定义的 C++ 析构方法,是否有用到引用计数表
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);
}
}

object_dispose 方法内部调用了 objc_destructInstance 方法。

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;
}

objc_destructInstance 方法中对 C++ 析构方法和关联对象进行了处理。

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)) {
// 有弱引用或者使用了引用计数表
clearDeallocating_slow();
}

assert(!sidetable_present());
}

我们这里只管理 weak 表怎么处理,所以只看 clearDeallocating_slow 方法。

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_clear_no_lock 是最终进行清空弱引用的方法。

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 先查找对应 weak_entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
// 没找到直接返回
return;
}

// zero out references
weak_referrer_t *referrers;
size_t count;

if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}

// 遍历 referrers 数组
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
// referrers 存储的是指向 weak 指针的指针,进行取值操作就能获取到存储的 referent 对象
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_table 中移除 entry
weak_entry_remove(weak_table, entry);
}

总结

  1. weak 底层原理是有一张全局 weak_table_t 结构的 hash 表,key 是对象的地址,value 是 weak 指针的指针数组,数组中存储了该对象的所有 weak 引用。
  2. 使用 weak 关键字修饰时,会执行弱引用添加操作。对象释放时,会清空 weak 指针的指针数组。