iOS weak原理

iOS weak原理 ([參考地址]:(http://www.cocoachina.com/articles/11990) )

Runtime維護了一個weak表,用于存儲指向某個對象的所有weak指針。weak表其實是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數(shù)組。
注意:{key :value<NSArray>},value是一個數(shù)組類型,數(shù)組里面放的是weak指針的地址,weak指針內(nèi)存放的值為所指對向的地址。

weak 的實現(xiàn)原理可以概括一下三步:
1、初始化時:runtime會調(diào)用objc_initWeak函數(shù),初始化一個新的weak指針指向?qū)ο蟮牡刂贰?br> 2、添加引用時:objc_initWeak函數(shù)會調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表。
3、釋放時,調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。

下面將開始詳細(xì)介紹每一步:
1、初始化時:runtime會調(diào)用objc_initWeak函數(shù),objc_initWeak函數(shù)會初始化一個新的weak指針指向?qū)ο蟮牡刂贰?br> 示例代碼:
NSObject *obj = [[NSObject alloc] init];id __weak obj1 = obj;
當(dāng)我們初始化一個weak變量時,runtime會調(diào)用 NSObject.mm 中的objc_initWeak函數(shù)。這個函數(shù)在Clang中的聲明如下:
id objc_initWeak(id *object , id value);
而對于 objc_initWeak() 方法的實現(xiàn)
id objc_initWeak(id *location ,id newObj) {// 查看對象實例是否有效// 無效對象直接導(dǎo)致指針釋放if(!newObj) { *location = nil; return nil; }
可以看出,這個函數(shù)僅僅是一個深層函數(shù)的調(diào)用入口,而一般的入口函數(shù)中,都會做一些簡單的判斷(例如 objc_msgSend 中的緩存判斷),這里判斷了其指針指向的類對象是否有效,無效直接釋放,不再往深層調(diào)用函數(shù)。否則,object將被注冊為一個指向value的__weak對象。而這事應(yīng)該是objc_storeWeak函數(shù)干的。

注意:*objc_initWeak函數(shù)有一個前提條件:就是object必須是一個沒有被注冊為__weak對象的有效指針。而value則可以是null,或者指向一個有效的對象。
2、添加引用時:objc_initWeak函數(shù)會調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表。
id objc_storeWeak(id *location, id newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
......
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
oldObj = location;
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
......
if (
location != oldObj) {
OSSpinLockUnlock(lock1);

if SIDE_TABLE_STRIPE > 1

    if (lock1 != lock2) OSSpinLockUnlock(lock2);

endif

    goto retry;
}
if (oldObj) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (newObj) {
    newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
    // weak_register_no_lock returns NULL if weak store should be rejected
}
// Do not set *location anywhere else. That would introduce a race.
*location = newObj;
......
return newObj;

}

SideTable 這個結(jié)構(gòu)體,我給他起名引用計數(shù)和弱引用依賴表,因為它主要用于管理對象的引用計數(shù)和 weak 表。在 NSObject.mm 中聲明其數(shù)據(jù)結(jié)構(gòu):
class SideTable {
private:
static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
public:
RefcountMap refcnts ; // 引用計數(shù)的 hash 表
weak_table_t weak_table; // weak 引用全局 hash 表
......
}
2)、weak表
weak表是一個弱引用表,實現(xiàn)為一個weak_table_t結(jié)構(gòu)體,存儲了某個對象相關(guān)的的所有的弱引用信息。
struct weak_table_t{
// 保存了所有指向指定對象的 weak 指針
weak_entry_t *weak_entries;
// 存儲空間
size_t num_entries;
// 參與判斷引用計數(shù)輔助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};

這是一個全局弱引用hash表。使用不定類型對象的地址作為 key ,用 weak_entry_t 類型結(jié)構(gòu)體對象作為 value 。其中的 weak_entries 成員,從字面意思上看,即為弱引用表入口。其實現(xiàn)也是這樣的。
其中weak_entry_t是存儲在弱引用表中的一個內(nèi)部結(jié)構(gòu)體,它負(fù)責(zé)維護和存儲指向一個對象的所有弱引用hash表。其定義如下:
struct weak_entry_t {
DisguisedPtr referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
......
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};

在 weak_entry_t 的結(jié)構(gòu)中,DisguisedPtr referent 是對泛型對象的指針做了一個封裝,通過這個泛型類來解決內(nèi)存泄漏的問題。從注釋中寫 out_of_line 成員為最低有效位,當(dāng)其為0的時候, weak_referrer_t 成員將擴展為多行靜態(tài) hash table。其實其中的 weak_referrer_t 是二維 objc_object 的別名,通過一個二維指針地址偏移,用下標(biāo)作為 hash 的 key,做成了一個弱引用散列。

那么在有效位未生效的時候,out_of_line 、 num_refs、 mask 、 max_hash_displacement 有什么作用?以下是筆者自身的猜測:

out_of_line:最低有效位,也是標(biāo)志位。當(dāng)標(biāo)志位 0 時,增加引用表指針緯度。

num_refs:引用數(shù)值。這里記錄弱引用表中引用有效數(shù)字,因為弱引用表使用的是靜態(tài) hash 結(jié)構(gòu),所以需要使用變量來記錄數(shù)目。

mask:計數(shù)輔助量。

max_hash_displacement:hash 元素上限閥值。

其實 out_of_line 的值通常情況下是等于零的,所以弱引用表總是一個 objc_objective 指針二維數(shù)組。一維 objc_objective 指針可構(gòu)成一張弱引用散列表,通過第三緯度實現(xiàn)了多張散列表,并且表數(shù)量為 WEAK_INLINE_COUNT 。

總結(jié)一下 StripedMap[] : StripedMap 是一個模板類,在這個類中有一個 array 成員,用來存儲 PaddedT 對象,并且其中對于 [] 符的重載定義中,會返回這個 PaddedT 的 value 成員,這個 value 就是我們傳入的 T 泛型成員,也就是 SideTable 對象。在 array 的下標(biāo)中,這里使用了 indexForPointer 方法通過位運算計算下標(biāo),實現(xiàn)了靜態(tài)的 Hash Table。而在 weak_table 中,其成員 weak_entry 會將傳入對象的地址加以封裝起來,并且其中也有訪問全局弱引用表的入口。

舊對象解除注冊操作 weak_unregister_no_lock
該方法主要作用是將舊對象在 weak_table 中解除 weak 指針的對應(yīng)綁定。根據(jù)函數(shù)名,稱之為解除注冊操作。從源碼中,可以知道其功能就是從 weak_table 中解除weak 指針的綁定。而其中的遍歷查詢,就是針對于 weak_entry 中的多張弱引用散列表。

新對象添加注冊操作 weak_register_no_lock

這一步與上一步相反,通過 weak_register_no_lock 函數(shù)把新的對象進行注冊操作,完成與對應(yīng)的弱引用表進行綁定操作。

3、釋放時,調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。

當(dāng)weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當(dāng)釋放對象時,其基本流程如下:

1、調(diào)用objc_release
2、因為對象的引用計數(shù)為0,所以執(zhí)行dealloc
3、在dealloc中,調(diào)用了_objc_rootDealloc函數(shù)
4、在_objc_rootDealloc中,調(diào)用了object_dispose函數(shù)
5、調(diào)用objc_destructInstance
6、最后調(diào)用objc_clear_deallocating
重點看對象被釋放時調(diào)用的objc_clear_deallocating函數(shù)。該函數(shù)實現(xiàn)如下:

voidobjc_clear_deallocating(id obj){assert(obj);assert(!UseGC);if(obj->isTaggedPointer())return; obj->clearDeallocating();}

也就是調(diào)用了clearDeallocating,繼續(xù)追蹤可以發(fā)現(xiàn),它最終是使用了迭代器來取weak表的value,然后調(diào)用weak_clear_no_lock,然后查找對應(yīng)的value,將該weak指針置空,weak_clear_no_lock函數(shù)的實現(xiàn)如下:
objc_clear_deallocating該函數(shù)的動作如下:
1、從weak表中獲取廢棄對象的地址為鍵值的記錄
2、將包含在記錄中的所有附有 weak修飾符變量的地址,賦值為nil
3、將weak表中該記錄刪除
4、從引用計數(shù)表中刪除廢棄對象的地址為鍵值的記錄
看了objc-weak.mm的源碼就明白了:其實Weak表是一個hash(哈希)表,然后里面的key是指向?qū)ο蟮牡刂?,Value是Weak指針的地址的數(shù)組。

補充:.m和.mm的區(qū)別
.m:源代碼文件,這個典型的源代碼文件擴展名,可以包含OC和C代碼。
.mm:源代碼文件,帶有這種擴展名的源代碼文件,除了可以包含OC和C代碼之外,還可以包含C++代碼。僅在你的OC代碼中確實需要使用C++類或者特性的時候才用這種擴展名。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容