關(guān)于 weak 的實(shí)現(xiàn)原理,網(wǎng)上已經(jīng)有各種詳細(xì)的文章了纷纫,大多貼了一段代碼涕蚤,然后根據(jù)代碼解釋流程,這里隨便找了一篇供參考:iOS weak的實(shí)現(xiàn)原理
就我個人而言戳玫,看完這些文章感覺還是理解不深,特別是數(shù)據(jù)結(jié)構(gòu)方面的靠抑,比如 weak 表量九,可能很多人都只有一個大概的認(rèn)知:weak 指針存放在weak表中适掰,這是一種哈希表颂碧,對象作為key,weak指針數(shù)組作為value类浪,然后就沒有了载城,沒有更清晰的認(rèn)知,這里根據(jù)源碼調(diào)試結(jié)果费就,主要談?wù)剋eak表相關(guān)的結(jié)構(gòu)诉瓦。
以下面代碼為例子:
NSObject *obj = [NSObject new];
__weak id weakObj = obj;
通過 objc_initWeak 最后走到關(guān)鍵方法 storeWeak(這里去掉部分代碼,保留關(guān)鍵邏輯)
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++類,通過重寫下標(biāo)操作符[]來實(shí)現(xiàn)key-value存取眠蚂,其內(nèi)部維護(hù)了一個數(shù)組煞聪,大小是 64,也就是最多可以存儲 64 個SideTable逝慧,這里以對象地址為key昔脯,返回一個SideTable啄糙,內(nèi)部原理大概是這樣的:
// StripedMap *map = &SideTables();
// newTable = map[newObj];
unsigned int index = someHashCode(newObj)%64;
newTable = map.array[index];
哈希表的大小是64,哈希沖突時采用數(shù)組存儲沖突的對象云稚,再看 SideTable 結(jié)構(gòu)(簡化版):
struct SideTable {
spinlock_t slock; // 自旋鎖
RefcountMap refcnts; // 引用計(jì)數(shù)
weak_table_t weak_table; // weak 指針
};
struct weak_table_t {
weak_entry_t *weak_entries;
};
其中 weak_table 用來存放weak指針相關(guān)的信息隧饼,內(nèi)部是一個數(shù)組weak_entries,數(shù)組的元素是 weak_entry_t静陈,這個 weak_entry_t 是最后weak指針存放的位置燕雁,一個對象對應(yīng)一個 weak_entry_t,這里的數(shù)組 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指針指向它,默認(rèn)情況下角撞,weak指針存放在 inline_referrers 中呛伴,當(dāng) inline_referrers 存滿后(超過4個)就會轉(zhuǎn)到 referrers 中動態(tài)分配內(nèi)存存儲。
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指針數(shù)組
weak_table_t table_t = newTable->weak_table;
// 創(chuàng)建 entry_t 存儲 weakObj 和 obj 的對應(yīng)關(guān)系
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指針的基本過程谒所,剔除了海量細(xì)節(jié)热康,比如已存在obj weak指針的處理,但是只要清楚了數(shù)據(jù)結(jié)構(gòu)劣领,其他方面沒有太多的黑魔法姐军,都是正常的操作。
至于weak指針的釋放尖淘,在 obj dealloc 方法里奕锌,通過一系列調(diào)用,最后會調(diào)用到 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) 對應(yīng)的 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個就會轉(zhuǎn)移到 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 數(shù)組中移除 entry
weak_entry_remove(weak_table, entry);
}
weak 的原理大體上就是這樣,需要注意的是對于 TaggedPointer 對象趁桃,不走這一套流程辽话,也就是不存儲 TaggedPointer 對象的 weak 指針,因?yàn)?TaggedPointer 對象是存儲在指針本身里的卫病,是在棧區(qū)油啤,不是堆區(qū),內(nèi)存由系統(tǒng)自動管理蟀苛。