問題:為何weak修飾的變量可以打破循環(huán)引用?
因為weak修飾的變量存儲在散列表中的弱引用表里,不參與引用計數(shù)器的使用帮匾,也就是說瞻离,在進(jìn)行釋放額時候,不管你怎么引用献汗,直接就把你置空了。
基本概念
- SideTable :散列表:系統(tǒng)自動維護(hù),用于存儲/管理一些信息
SideTable的結(jié)構(gòu)中能看到 其管理三種表:
spinlock_t slock
自旋鎖表;
RefcountMap refcnts
引用計數(shù)表;
weak_table_t weak_table
弱引用表; - weak_table:弱引用對象存儲的表摹迷,是SideTable中的一張表
- weak_entry_t:weak_table里面的一個單元,用于管理當(dāng)前類的弱引用對象郊供,可以理解為一個數(shù)組峡碉,查看weak_entry_t的結(jié)構(gòu),有助于更好的理解里面的存儲結(jié)構(gòu)驮审,里面包含一個weak_referrer_t鲫寄,相當(dāng)于一個數(shù)組,這里面的就是存儲的弱引用對象疯淫,還有其他的一些信息塔拳,比如mask(蒙版,容量-1)峡竣、num_refs (當(dāng)前存儲的數(shù)量)等
- weak_referrer_t :weak_entry_t 中的弱引用對象靠抑,相當(dāng)于是 數(shù)組中的一個元素
存儲原理
1、源碼探索入口
寫上這樣的代碼适掰,打上斷點颂碧,并打開匯編模式:debug->debug workflow -> alway show disassembly
- (instancetype)init
{
if (self = [super init]) {
? id __weak weakSelf = self; //斷點在這
}
return self;
}
運行后會進(jìn)入斷點荠列,出現(xiàn)這樣的一些信息
找到callq方法:objc_initWeak
,拿到這個方法就可以進(jìn)入源碼去調(diào)試了
源碼探索
id objc_initWeak(id *location, id newObj)
{
.....
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);
}
1.1、內(nèi)部做的操作是 存儲weak—storeWeak
static id
storeWeak(id *location, objc_object *newObj)
{
......
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
//有新值载城,判斷類有沒有初始化肌似,如果沒有初始化,就進(jìn)行初始化
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
goto retry;
}
}
// 有舊值诉瓦,進(jìn)行 weak_unregister_no_lock操作
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 有新值 進(jìn)行weak_register_no_lock 操作
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
}
else {
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
關(guān)鍵步驟:
1川队、如果cls還沒有 初始化,先初始化這個類
2睬澡、如果weak對象有舊值固额,先對舊值 進(jìn)行 weak_unregister_no_lock
,刪除舊值
3煞聪、如果weak對象有新值 就對新值進(jìn)行weak_register_no_lock
斗躏,新增新值
1.2、再來看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;
// 在 weak_table 中去找到 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到了 這個 entry昔脯, 就刪除 entry 中的 引用對象-referrer
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 中的引用對象 沒有了 刪除這個 entry
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
關(guān)鍵步驟
1啄糙、在 weak_table 中去找到 有 referent-引用對象的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
2、如果找到了 entry 就刪除 entry中的 referent-引用對象
3云稚、判斷 entry 里面 還有沒有其他對象隧饼,如果沒有,就把entry也remove掉(相當(dāng)于數(shù)組中的元素為空静陈,就把這個數(shù)據(jù)也刪掉)
1.3桑李、 存儲新值:weak_register_no_lock
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
......
weak_entry_t *entry;
// 在 weak_table 中去找到 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 如果找到,直接append
append_referrer(entry, referrer);
}
else {
// 如果沒有找到相應(yīng)的 entry 窿给,就創(chuàng)建一個entry 并插入 weak_table
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;
}
關(guān)鍵步驟
1贵白、在 weak_table 中去找 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
2、如果找到entry崩泡,進(jìn)行添加操作:append_referrer
- 2.1禁荒、如果有空位,直接插進(jìn)去
---- 這里有一個疑問:為什么會有一個空位角撞?這里可以看new_entry的實現(xiàn):初始容量為4呛伴,并默認(rèn)4個空值
- 2.2、如果數(shù)量超過容量的3/4谒所,進(jìn)行擴容热康,再添加(這里想到,方法緩存機制劣领,方法緩存也是超過3/4進(jìn)行擴容姐军,方法的擴容是:擴容之后,以前的方法刪掉了,再把需要緩存的方法插進(jìn)去)
3奕锌、如果沒找到entry著觉,創(chuàng)建一個entry,在進(jìn)行插入
大概的過程是這樣的:
釋放原理
弱引用對象在釋放的時候惊暴,可以在dealloc中去看具體是怎么釋放的
dealloc -》
rootDealloc -》
object_dispose -》
objc_destructInstance -》
clearDeallocating -》
clearDeallocating_slow
void *objc_destructInstance(id obj)
{
if (obj) {
bool assoc = obj->hasAssociatedObjects();
// 如果有關(guān)聯(lián)對象饼丘,就remove掉
if (assoc) _object_remove_assocations(obj);
// 弱引用的釋放在這里
obj->clearDeallocating();
}
return obj;
}
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)) {
// 在這里進(jìn)行釋放
clearDeallocating_slow();
}
assert(!sidetable_present());
}
// 找到散列表中的 weak_table 表,找到weak_table 中的 entry辽话,將entry中的 引用對象referrer 置空肄鸽,最后remove entry
NEVER_INLINE void
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_table 表油啤,找到weak_table 中的 entry典徘,將entry中的 引用對象referrer 置空,最后remove entry
最后補充一張存儲結(jié)構(gòu)圖 來源
補充
1村砂、strong
同樣的方法,進(jìn)入strong底層屹逛,可以看到础废,底層默認(rèn)實現(xiàn)的是 retain 方法,所以在arc中罕模,strong其實等同于 retrain2评腺、weak與assign
assign一般只修飾值類型,雖然也可以修飾引用類型淑掌,但是修飾的對象釋放后蒿讥,指針不會自動被置空,此時向?qū)ο蟀l(fā)消息會崩潰抛腕。
weak 不會產(chǎn)生野指針問題芋绸。因為weak修飾的對象釋放后(引用計數(shù)器值為0),指針會自動被置nil担敌,之后再向該對象發(fā)消息也不會崩潰摔敛。 weak是安全的。