1、__weak
修飾符的使用案例
在開發(fā)的過程中,可能回遇到循環(huán)引用的問題蚁堤。所謂循環(huán)引用,當(dāng)對象A
持有了對象B
但狭,與此同時(shí)對象B
同時(shí)也持有對象A
時(shí)违寿,此時(shí)對象A
銷毀需要對象B
先銷毀,而對象B
銷毀同樣需要對象A
先銷毀熟空,就導(dǎo)致相互等待銷毀藤巢,此時(shí)對象A
,對象B
的引用計(jì)數(shù)都不為0息罗,所以對象A
掂咒,對象B
此時(shí)都無法釋放。 從而導(dǎo)致了內(nèi)存泄漏迈喉。
解決循環(huán)引用最常用的方法就是使用__weak
修飾符绍刮。
- (void)weakSelf{
self.name = @"jack";
//使用__weak修飾符后,若引用self挨摸,self的引用計(jì)數(shù)不會增加孩革。
__weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
};
block();
}
2、weak引用的流程圖
對
__weak
打斷點(diǎn)得运,查看相關(guān)的匯編代碼膝蜈,可以看到接下來要跳轉(zhuǎn)的方法為objc_initWeak
锅移。在源碼中查找該方法,該方法在NSObject.mm
文件下饱搏。
方法:id objc_initWeak(id *location, id newObj)
-
id *location
:弱應(yīng)用修飾的對象的位置 -
id newObj
:被若引用的對象
weak的相關(guān)底層方法:
從這些方法可以看出非剃,弱引用是存儲在sideTable中。sideTable具體內(nèi)容這里不進(jìn)行說明推沸,只展示相關(guān)的數(shù)據(jù)結(jié)構(gòu)备绽,以便于后續(xù)的闡述。
(1). 使用新的obj
創(chuàng)建一個(gè)sideTable
:SideTable * newTable = &SideTables()[newObj];
*a鬓催、使用新值的指針肺素,獲得以 newObj 為索引所存儲的值地址。通過hash找到newObj所在的SideTable使用weak引者的地址---disguise_ptr(oldObj)宇驾,在SideTables中压怠,查找到改地址對應(yīng)的sideTable
(2). 使用原有的obj
獲取到對應(yīng)的sideTable
:oldTable = &SideTables()[oldObj];
*a、使用weak引者的地址---disguise_ptr(oldObj)飞苇,在SideTables中,查找到改地址對應(yīng)的sideTable
(3). 使用weak_table
和referent
來查找相關(guān)的entry:
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
*a蜗顽、通過weak_table
獲取到weak_entries
(weak_table->weak_entries
)
*b布卡、遍歷weak_entries
,查找可以匹配referent
的entry
雇盖。[這里是通過對referent
的哈希運(yùn)算忿等,并與weak_table->mask
進(jìn)行進(jìn)行與操作,獲取到index
(與mask
的與操作崔挖,可以有效的防止越界操作)贸街,在index
位置沒有匹配的referent
,會自動向后匹配狸相。以weak_table->max_hash_displacement
為匹配的邊界]
/**********************************************************************/
/*>>>>>計(jì)算出來entry在weak_table->weak_entries位置的index<<<<<*/
/**********************************************************************/
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
//weak_entries中取出index位置的referent薛匪,并不是要查詢的被引用obj,繼續(xù)循環(huán)
while (weak_table->weak_entries[index].referent != referent) {
//地址往后+1脓鹃,并計(jì)算出來新的index
//這個(gè)與操作可以有效的防止index的越界
index = (index+1) & weak_table->mask;
//index和begin相等逸尖,即證明所有的entries已經(jīng)遍歷完成。
if (index == begin) bad_weak_table(weak_table->weak_entries);
//遍歷次數(shù)超過了max_hash_displacement瘸右,直接返回nil
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
*c娇跟、查找邏輯
(4). 從entry
的引用中,清除掉referrer
:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
*a太颤、清除一個(gè)referrer
苞俘,是遍歷entry
的referrers
(entry->referrers[index]
),匹配對應(yīng)的referrer
*b龄章、最重要的工作還是要查找referrer
對應(yīng)的index
吃谣。[這里是通過對referrer
的哈希運(yùn)算乞封,并與entry->mask
進(jìn)行進(jìn)行與操作,獲取到index
(與mask
的與操作基协,可以有效的防止越界操作)歌亲,在index
位置沒有匹配的referrer
,也是會進(jìn)行后向匹配澜驮。以entry->max_hash_displacement
為匹配的邊界]
/**********************************************************************/
/*>>>>>計(jì)算出來referre在entry->referrers若引用位置的index<<<<<*/
/**********************************************************************/
//hash函數(shù)計(jì)算出來位置陷揪,
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//從計(jì)算出來的位置開始查詢
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
//已經(jīng)超出了max_hash_displacement,報(bào)錯objc_weak_error杂穷,并返回
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;
}
}
*c悍缠、找到了對應(yīng)的referrer以后,把查詢到的index位置的指向置為nil耐量,即從referrers中刪除了改referrer飞蚓。enry的num_refs--操作
/**********************************************************************/
/*>>>>>找到了需要清除的referrer的位置,進(jìn)行刪除操作廊蜒,并且把引用計(jì)數(shù)減1<<<<<*/
/**********************************************************************/
//找到了趴拧,從referre中刪除這個(gè)弱引用對象
entry->referrers[index] = nil;
//引用計(jì)數(shù)減1
entry->num_refs--;
*d、刪除referrer操作
(5). 刪除entry
的操作山叮,即刪除entry
中referrers
中為空的entry
:
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
*a著榴、當(dāng)一個(gè)entry中,若引用對象的數(shù)量為0了屁倔,也就是referrers鏈表沒有數(shù)據(jù)脑又,這個(gè)entry就需要清除掉
*b、清除掉weak_table的一個(gè)entry后锐借,需要處理weak_table的num_entries问麸,進(jìn)行減1操作
/**********************************************************************************/
/*>>>>>清除掉weak_table->entries中為空的entry,并且weak_table->num_entries減1操作<<<<<*/
/**********************************************************************************/
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//壓weak_entryr中的entries的空間
/*
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
*/
weak_compact_maybe(weak_table);
*c钞翔、weak_table中清除entry
(6). 創(chuàng)建entry并插入到一個(gè)weak_table的entries中:
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
*a严卖、當(dāng)一個(gè)對象被弱引用,并且第一次被弱引用布轿,首先要創(chuàng)建一個(gè)引用對象和被引用的對象entry
/*********************/
/*>>>>>新建entry<<<<<*/
/*********************/
//新建entry妄田,
weak_entry_t new_entry(referent, referrer);
//判斷entries是否需要擴(kuò)容,需要的話驮捍,擴(kuò)容原來尺寸的2倍
weak_grow_maybe(weak_table);
*b疟呐、把新建的entry
插入到weak_table->weak_entries
[和刪除entry
時(shí)一樣的操作,第一步是要根據(jù)referent
通過hash函數(shù)計(jì)算出來index
(需要和weak_table->mask
進(jìn)行與操作东且,防止越界)启具,查詢index
處是否有數(shù)據(jù)。有數(shù)據(jù)的話珊泳,后向查詢下一個(gè)位置鲁冯,直到找到對應(yīng)的index
拷沸。或者超出weak_table
的最大存放數(shù)量]
/***************************************************************/
/*>>>>>計(jì)算新建的entry在weak_table->weak_entries的的位置index<<<<<*/
/**************************************************************/
//獲取到weak_entries
weak_entry_t *weak_entries = weak_table->weak_entries;
//函數(shù)函數(shù)計(jì)算存儲位置(理解為數(shù)組下標(biāo))
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
//查找到位index的位置薯演,如果有數(shù)據(jù)撞芍,就繼續(xù)查找下一個(gè)位置
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
*c、計(jì)算出來所在的位置后跨扮,進(jìn)行插入操作序无,并且把weak_table->num_entries進(jìn)行加1操作
//找到空位置index,把new_entery插入到index位置
weak_entries[index] = *new_entry;
//弱引用計(jì)數(shù)加1
weak_table->num_entries++;
*d衡创、新建的entry
插入到weak_table_entries
中
(7). 已經(jīng)被弱引用的對象referent帝嗡,新加增弱引用操作。即entry->referres新增一個(gè)對象:
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
*a璃氢、在已經(jīng)被弱引用的前提下哟玷,再次被另一個(gè)對象進(jìn)行弱應(yīng)用,就要在entry->referres新增一個(gè)成員
*b一也、在插入新的referre之前巢寡,會先判斷referrers是不是需要進(jìn)行擴(kuò)容。如需要擴(kuò)容椰苟,擴(kuò)充為原來的兩倍
/********************/
/*>>>>>擴(kuò)容的操作<<<<<*/
/********************/
//referrers的帶下尺寸抑月,可以看出,如果需要擴(kuò)容的話尊剔,是按照old_size的兩倍進(jìn)行開辟空間的
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
*c、和其他的操作一樣菱皆,還時(shí)必須要計(jì)算出來referrer所在的位置index
/******************************************************/
/*>>>>>計(jì)算新的referrer所在的index<<<<<*/
/*>>>>>w_hash_pointer(new_referrer) & (entry->mask);<<<<<*/
/******************************************************/
//計(jì)算出來在entry中的index位置
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//查找index位置是不是有數(shù)據(jù)须误。有的話。就往后加一個(gè)位置仇轻,繼續(xù)查詢
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;
}
*d京痢、將referrer
插入到計(jì)算出來的index
處
//取到index的地址,進(jìn)行referrer的存儲
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
//弱引用指針個(gè)數(shù)加1
entry->num_refs++;
*e篷店、將referrer
插入到計(jì)算出來的index
處
3祭椰、對象銷毀時(shí),weak引用的處理
當(dāng)對象的引用計(jì)數(shù)為0的時(shí)候疲陕,或者調(diào)用了dealloc
方法方淤,runtime會調(diào)用_objc_rootDealloc
。
- 1蹄殃、一個(gè)對象銷毀的時(shí)候携茂,會查詢isa中的weakly_referenced參數(shù),如果是1的話诅岩,需要清理弱引用讳苦。
- 2带膜、清理的時(shí)候,使用weak_table和referent查找到entry
- 3鸳谜、遍歷entry中的referres膝藕,把里面的對象置為nil
- 4、清理完成referrers后咐扭,刪除掉entry