关于 weak 的实现原理,网上已经有各种详细的文章了,大多贴了一段代码,然后根据代码解释流程,这里随便找了一篇供参考:iOS weak的实现原理
就我个人而言,看完这些文章感觉还是理解不深,特别是数据结构方面的,比如 weak 表,可能很多人都只有一个大概的认知:weak 指针存放在weak表中,这是一种哈希表,对象作为key,weak指针数组作为value,然后就没有了,没有更清晰的认知,这里根据源码调试结果,主要谈谈weak表相关的结构。
以下面代码为例子:
NSObject *obj = [NSObject new];
__weak id weakObj = obj;
通过 objc_initWeak 最后走到关键方法 storeWeak(这里去掉部分代码,保留关键逻辑)
static id storeWeak(id *location, objc_object *newObj)
{
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;
}
// Clean up old value, if any.
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.
}
return (id)newObj;
}
先看代码 newTable = &SideTables()[newObj];
这里的 newObj 就是我们上面的NSObject 对象 obj,这段代码可以看成如下三行:
// newTable = &SideTables()[newObj];
SideTable * newTable = nil;
StripedMap *map = &SideTables();
newTable = map[newObj];
其中 StripedMap 是一个全局的哈希表(我们常说的weak表就是这个),这是一个C++类,通过重写下标操作符[]来实现key-value存取,其内部维护了一个数组,大小是 64,也就是最多可以存储 64 个SideTable,这里以对象地址为key,返回一个SideTable,内部原理大概是这样的:
// StripedMap *map = &SideTables();
// newTable = map[newObj];
unsigned int index = someHashCode(newObj)%64;
newTable = map.array[index];
哈希表的大小是64,哈希冲突时采用数组存储冲突的对象,再看 SideTable 结构(简化版):
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引用计数
weak_table_t weak_table; // weak 指针
};
struct weak_table_t {
weak_entry_t *weak_entries;
};
其中 weak_table 用来存放weak指针相关的信息,内部是一个数组weak_entries,数组的元素是 weak_entry_t,这个 weak_entry_t 是最后weak指针存放的位置,一个对象对应一个 weak_entry_t,这里的数组 weak_entries 就是用来存放不同对象的weak指针,再来看 weak_entry_t:
struct weak_entry_t {
// 存储对象 obj
DisguisedPtr<objc_object> referent;
union {
struct {
// 存储weak指针 weakObj
weak_referrer_t *referrers;
};
struct {
// 存储weak指针 weakObj
// WEAK_INLINE_COUNT = 4;
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
其中 referent 用来存放对象,referrers ,inline_referrers 用来存储 weak指针,一个对象可以有多个weak指针指向它,默认情况下,weak指针存放在 inline_referrers 中,当 inline_referrers 存满后(超过4个)就会转到 referrers 中动态分配内存存储。
NSObject *obj = [NSObject new];
__weak id weakObj = obj;
用伪伪伪代码表示上面代码weak指针的存储过程:
storeWeak(id * weakObj, id obj) {
// 获取全局 StripedMap 哈希表对象
StripedMap *map = &SideTables();
// 获取 obj 所映射位置的 SideTable
SideTable * newTable = map[obj];
// 获取 weak指针数组
weak_table_t table_t = newTable->weak_table;
// 创建 entry_t 存储 weakObj 和 obj 的对应关系
weak_entry_t entry_t;
entry_t.referent = obj;
entry_t.inline_referrers[0] = weakObj;
// 插入
table_t.weak_entries->insert(entry_t);
// SideTable * newTable = map[obj];
// newTable->weak_table.weak_entries->insert(entry_t);
}
上述就是存储一个weak指针的基本过程,剔除了海量细节,比如已存在obj weak指针的处理,但是只要清楚了数据结构,其他方面没有太多的黑魔法,都是正常的操作。
至于weak指针的释放,在 obj dealloc 方法里,通过一系列调用,最后会调用到 weak_clear_no_lock 中,核心代码如下(删删删):
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 找到对象 referent_id(就是上面的obj) 对应的 weak_entry_t
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;
// 前面说过,对象 referent_id 的 weak指针超过4个就会转移到 referrers 中
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else { // 否则在存在 inline_referrers 中
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
// 找到所有的 weak 指针
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
// weak 指针置为 nil
*referrer = nil;
}
}
}
// 从 weak_table 的 weak_entries 数组中移除 entry
weak_entry_remove(weak_table, entry);
}
weak 的原理大体上就是这样,需要注意的是对于 TaggedPointer 对象,不走这一套流程,也就是不存储 TaggedPointer 对象的 weak 指针,因为 TaggedPointer 对象是存储在指针本身里的,是在栈区,不是堆区,内存由系统自动管理。
网友评论