浅析weak指针的实现
oc中weak指針主要用于打破循環(huán)或者防止循環(huán)引用的發(fā)生,應(yīng)用場(chǎng)景還是很廣泛的。那么被weak修飾的指針與被指向的對(duì)象在底層的運(yùn)作機(jī)制究竟怎樣的呢?為什么在對(duì)象釋放銷毀時(shí)weak指針能自動(dòng)置為nil,從而避免了野指針的錯(cuò)誤?
weak指針實(shí)現(xiàn)原理
當(dāng)對(duì)象被一個(gè)weak指針引用時(shí),底層的實(shí)現(xiàn)原理就是:不對(duì)被引用的對(duì)象進(jìn)行retain,而是利用哈希表對(duì)weak指針與被指向的對(duì)象進(jìn)行標(biāo)記、關(guān)聯(lián)。當(dāng)對(duì)象銷毀釋放內(nèi)存管時(shí)通過之前的標(biāo)記對(duì)weak指針地址進(jìn)行查找,最后把weak指針的指向置為nil。
weak指針實(shí)現(xiàn)源碼分析
首先weak實(shí)現(xiàn)的函數(shù)調(diào)用順序如圖
完成weak指針標(biāo)記需要處理的對(duì)象順序如下圖所示:關(guān)于SideTable以及根據(jù)對(duì)象指針查找對(duì)應(yīng)SideTable的分析在之前分析對(duì)象引用計(jì)數(shù)的文章中有相關(guān)的說明,這里就不再重復(fù)了。這里主要分析下處理weak_entry_t結(jié)構(gòu)體的函數(shù)
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) {// now remember it and where it is being storedweak_entry_t *entry;if ((entry = weak_entry_for_referent(weak_table, referent))) { //if 判斷 是否把weak指針指向的對(duì)象是否已經(jīng)有對(duì)應(yīng)的entry,有的話代表對(duì)象以前有被弱引用指向append_referrer(entry, referrer); //把 referrer 存進(jìn) 對(duì)應(yīng)的 entry} else { //對(duì)象第一次被弱指針引用weak_entry_t new_entry(referent, referrer);weak_grow_maybe(weak_table); //weak_table.weak_entries 擴(kuò)容處理weak_entry_insert(weak_table, &new_entry); //吧 weak_entry_t 放入 weak_table.weak_entries中數(shù)組,通過referent的哈希運(yùn)算&mask得到索引}// Do not set *referrer. objc_storeWeak() requires that the // value not change.return referent_id; } 復(fù)制代碼這里的邏輯主要是通過判斷查找對(duì)象對(duì)應(yīng)SideTable的weak_table屬性中是否包含有對(duì)應(yīng)的weak_entry_t結(jié)構(gòu)體,查找的實(shí)現(xiàn)主要是通過把對(duì)象指針經(jīng)過一定的哈希算法運(yùn)算后與上一個(gè)特定的數(shù)值(該數(shù)值通常是要查找順序表的最大索引)后得出的索引,再根據(jù)索引查找到特定的值與要查找的值對(duì)比得出結(jié)果。具體邏輯可以從源碼中看weak_entry_for_referent這個(gè)函數(shù)的實(shí)現(xiàn)。
對(duì)象第一次被弱引用指向處理
上面的else語句就是處理對(duì)象第一次被弱引用指向的處理。通過生成一個(gè)weak_entry_t結(jié)構(gòu)體后,把其插入到weak_table中,插入的邏輯與上面判斷是否含有特定weak_entry_t的邏輯都是一樣的,通過哈希算法獲得所以,然后插入相應(yīng)的索引位置,這里還有一個(gè)對(duì)weak_table進(jìn)行擴(kuò)容的處理weak_grow_maybe(weak_table);主要是判斷weak_table的存放對(duì)象容量大于或等于總?cè)萘康?/4時(shí)對(duì)weak_table的進(jìn)行兩倍容量的擴(kuò)容后,再把舊數(shù)據(jù)復(fù)制到擴(kuò)容后的內(nèi)存中。
對(duì)象之前已經(jīng)被弱引用指向過
這種情況就相對(duì)復(fù)雜一點(diǎn),首先我們看下最終存放weak指針地址的結(jié)構(gòu)體定義
struct weak_entry_t {//被引用的對(duì)象指針(經(jīng)過包裝處理)DisguisedPtr<objc_object> referent; //聯(lián)合體,用于存放weak指針地址union {struct {weak_referrer_t *referrers; //8字節(jié) 當(dāng)inline_referrers不夠存放數(shù)據(jù)的時(shí)候,使用該指針在堆上開辟空間存放數(shù)據(jù)uintptr_t out_of_line_ness : 2;uintptr_t num_refs : PTR_MINUS_2;//8字節(jié)uintptr_t mask;//8字節(jié)uintptr_t max_hash_displacement;//8字節(jié)};struct {// out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // 32字節(jié)};}; } 復(fù)制代碼weak_entry_t結(jié)構(gòu)體主要是通過上面的聯(lián)合體(union)來存放對(duì)象的weak指針地址。代碼中的聯(lián)合體分為兩部分,內(nèi)存大小為64個(gè)字節(jié),在添加weak指針的時(shí)候優(yōu)先使用的是inline_referrers數(shù)組存放,如果數(shù)組的已經(jīng)存滿了數(shù)據(jù)(4個(gè)指針),就會(huì)用上面的結(jié)構(gòu)體的數(shù)組指針來在堆上開辟空間來存放數(shù)據(jù),這樣就可以存放較多的weak指針地址。
weak指陣自動(dòng)置nil原理
如果對(duì)象有被weak指針指向的話,在對(duì)象銷毀釋放內(nèi)存執(zhí)行-dealloc方法的時(shí)候,根據(jù)函數(shù)的調(diào)用順序會(huì)執(zhí)行到weak_clear_no_lock這個(gè)方法。我們來看下次方法是怎樣使weakPointer = nil
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {objc_object *referent = (objc_object *)referent_id;weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);weak_referrer_t *referrers;size_t count;if (entry->out_of_line()) {referrers = entry->referrers;count = TABLE_SIZE(entry);} else {referrers = entry->inline_referrers;count = WEAK_INLINE_COUNT;}for (size_t i = 0; i < count; ++i) {objc_object **referrer = referrers[i];if (referrer) {if (*referrer == referent) {*referrer = nil;}}}}weak_entry_remove(weak_table, entry); }復(fù)制代碼可以看出上面代碼主要是獲取到對(duì)應(yīng)對(duì)象的weak_entry_t *entry,根據(jù)其內(nèi)部結(jié)構(gòu)體獲取到存放的weak指針地址的個(gè)數(shù),然后遍歷存放的weak指針的內(nèi)存,把weak指針置為nil。
總結(jié)
以上是生活随笔為你收集整理的浅析weak指针的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [2018.12.26]BZOJ1022
- 下一篇: 百度献礼高校开学季:AI Studio教