在日常開(kāi)發(fā)過(guò)程中嗅绸,經(jīng)常會(huì)出現(xiàn)循環(huán)引用而導(dǎo)致的內(nèi)存泄露的問(wèn)題,比如我們有a断国,b兩個(gè)對(duì)象贤姆,對(duì)象中都有兩個(gè)屬性name和age,然后出現(xiàn)了下列情況
a.name = b.name;
b.age = a.age;
- a對(duì)象通過(guò)a.name引用了b對(duì)象稳衬,所以b的引用計(jì)數(shù)為1
- b對(duì)象通過(guò)b.age引用了a對(duì)象庐氮,所以a的引用計(jì)數(shù)也為1
這時(shí)a執(zhí)行完任務(wù)后,發(fā)現(xiàn)引用計(jì)數(shù)還是1宋彼,不能釋放弄砍。b執(zhí)行完任務(wù)后,發(fā)現(xiàn)引用計(jì)數(shù)還是1输涕,也不能釋放音婶。
我們看到它們彼此都需要對(duì)方,然后boom莱坎!循環(huán)引用衣式。
這時(shí)候我們可以加一個(gè)__weak修飾,
__weak a.name = b.name;
b.age = a.age;
- a對(duì)象通過(guò)__weak a.name弱引用了b對(duì)象,所以b的引用計(jì)數(shù)為0碴卧。
- b對(duì)象通過(guò)b.age引用了a對(duì)象弱卡,所以a的引用計(jì)數(shù)也為1。
這時(shí)住册,a執(zhí)行完任務(wù)后婶博,發(fā)現(xiàn)引用計(jì)數(shù)還是1,不能釋放荧飞。b在執(zhí)行完任務(wù)后引用計(jì)數(shù)變?yōu)?凡人,b對(duì)象被釋放,從而a的引用計(jì)數(shù)減1變成0叹阔,也被釋放挠轴。
那么weak在這里面是如何執(zhí)行的呢?下面開(kāi)始我們的正題耳幢。
上圖是weak修飾對(duì)象時(shí)系統(tǒng)底層的流程岸晦,下面結(jié)合runtime源碼梳理一下。
1睛藻、objc_initWeak初始化
//location是弱引用對(duì)象的內(nèi)存地址
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
2委煤、調(diào)用storeWeak函數(shù)存儲(chǔ)weak弱引用指針
直接看代碼
static id
storeWeak(id *location, objc_object *newObj)
{
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//如果之前存儲(chǔ)過(guò)這個(gè)弱引用對(duì)象
if (haveOld) {
//則把原來(lái)弱引用對(duì)象從weak_table中移除
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
//如果這是新的弱引用對(duì)象
if (haveNew) {
//把這個(gè)新的弱引用對(duì)象添加到weak_table中
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
//判斷這個(gè)對(duì)象是否屬于TaggedPointer類(lèi)型的,如果屬于TaggedPointer類(lèi)型那就厲害了修档,根本不需要有引用計(jì)數(shù)一說(shuō)
//taggedPointer對(duì)象是為了蘋(píng)果為了性能最大化做的處理碧绞,針對(duì)不需要到棧和堆中尋找的對(duì)象,可以直接從地址中通過(guò)一定的算法得到他們的值吱窝。
if (newObj && !newObj->isTaggedPointer()) {
//標(biāo)記該對(duì)象是弱引用對(duì)象
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.
}
//給這兩個(gè)表解鎖
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
那這里面有兩個(gè)重要的函數(shù)weak_unregister_no_lock
和weak_register_no_lock
1)首先判斷弱引用指針表中是否有當(dāng)前要存儲(chǔ)的weak弱引用指針讥邻,看代碼
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) {
objc_object *referent = (objc_object *)referent_id;//被弱引用的對(duì)象
objc_object **referrer = (objc_object **)referrer_id;//弱引用變量的地址
weak_entry_t *entry;//弱引用指針條目
if (!referent) return;
//調(diào)用weak_entry_for_referent找到entry弱引用指針條目
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//從內(nèi)層inline_referrers中移除entry
//inline_referrers中只能存儲(chǔ)4個(gè)弱引用指針,多了就要存儲(chǔ)到referrers中院峡,所以要多一步empty判空操作
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;
}
}
}
//從weak_table中移除entry弱引用條目
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
2)調(diào)用weak_register_no_lock存儲(chǔ)entry弱引用指針條目兴使,看代碼
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;//被弱引用的對(duì)象
objc_object **referrer = (objc_object **)referrer_id;//弱引用變量的地址
//如果該弱引用對(duì)象是taggedPointer對(duì)象,則不做處理直接返回該對(duì)象
//taggedPointer對(duì)象是為了蘋(píng)果為了性能最大化做的處理照激,針對(duì)不需要到棧和堆中尋找的對(duì)象发魄,可以直接從地址中通過(guò)一定的算法得到他們的值。
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_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;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;//弱引用指針的條目
//判斷weak_table中是否有該條目
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//如果有俩垃,則把弱引用對(duì)象追加進(jìn)去
append_referrer(entry, referrer);
}
else {
//如果沒(méi)有励幼,則創(chuàng)建一個(gè)
weak_entry_t new_entry(referent, referrer);
//如果索引已經(jīng)超過(guò)原來(lái)的3/4,則給weak_table擴(kuò)容
weak_grow_maybe(weak_table);
//將新的entry插入weak_table
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
綜上口柳,就是系統(tǒng)在使用weak的存儲(chǔ)原理苹粟,當(dāng)然也可以繼續(xù)深入的探索,日后會(huì)抽時(shí)間繼續(xù)探索噠跃闹,希望能和大家共同進(jìn)步嵌削。
參考博客:
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法--SideTables毛好、RefcountMap、weak_table_t-一
Objective-C 小記(10)__weak
深入淺出ARC(上)