從Objective-C源碼看weak

在Objective-C中,一般為了解決循環引用的問題,我們會使用weak 修飾,使得一方不會持有另一方,解決循環引用的問題.

今天就從Objective-C的源碼中看一下weak是怎么被實現的,在NSObject.mm文件中可以找到一個這樣的函數

/** * Initialize a fresh weak pointer to some object location.?

?* It would be used for code like: *?

* (The nil case) * __weak id weakPtr;?

* (The non-nil case) * NSObject *o = ...;

?* __weak id weakPtr = o; *?

?* This function IS NOT thread-safe with respect to concurrent

?* modifications to the weak variable. (Concurrent weak clear is safe.) *

?* @param location Address of __weak ptr. *?

@param newObj Object ptr. */

id objc_initWeak(id *location, id newObj){

?if (!newObj) { *location = nil; return nil; }?

?return storeWeak(location, (objc_object*)newObj);

}

id objc_initWeakOrNil(id *location, id newObj){

?if (!newObj) { *location = nil; return nil; }?

?return storeWeak (location, (objc_object*)newObj);

}

從注釋中可以看到weak變量會調用這個兩個方法其中一個進行初始化.然后我們接著看后面的storeWeak方法

This function stores a new value into a __weak variable. It would be used anywhere a __weak variable is the target of an assignment

id objc_storeWeak(id *location, id newObj)

從注釋中可以很明顯看出這個方法就是用來存儲weak變量的

下面是方法的具體實現:

// If HaveOld is true, the variable has an existing value

//? that needs to be cleaned up. This value might be nil.

// If HaveNew is true, there is a new value that needs to be

//? assigned into the variable. This value might be nil.

// If CrashIfDeallocating is true, the process is halted if newObj is

//? deallocating or newObj's class does not support weak references.

//? If CrashIfDeallocating is false, nil is stored instead.

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;

//獲取SideTable

????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;

????......

????returnnewObj;

}

首先看注釋得到的答案是如果這個變量有老值,會先清除,如果是新值會存儲起來(這個值可以為nil),如果新值正在釋放或者是新值得類不支持weak,則會存儲一個nil代替.

首先會根據要存儲的對象去獲取SideTable,先分析一下SideTable的代碼

struct SideTable {?

?spinlock_t slock;?

?RefcountMap refcnts;?

//存儲weak_entry的hashTable

?weak_table_t weak_table;?

?SideTable() {?

?memset(&weak_table, 0, sizeof(weak_table));?

?}?

?~SideTable() {

?_objc_fatal("Do not delete SideTable.");?

?}?

?void lock() { slock.lock(); }?

?void unlock() { slock.unlock(); }?

?void forceReset() { slock.forceReset(); }?

?// Address-ordered lock discipline for a pair of side tables.?


static void lockTwo(SideTable *lock1, SideTable *lock2);?

? static void unlockTwo(SideTable *lock1, SideTable *lock2);

};

可以看到SideTable內部有一個成員變量weak_table,不難猜出這個weak_table是用來存儲weak_entry的,接下來繼續看weak_table的結構

The global weak references table. Stores object ids as keys,

and weak_entry_t structs as their values.

struct weak_table_t {

? ? weak_entry_t *weak_entries;

? ? size_t? ? num_entries;

? ? uintptr_t mask;

? ? uintptr_t max_hash_displacement;

};

可以看到weak_table_t就是一個內部包含一個weak_entries指針的結構體,繼續看weak_entry_t的結構

/**

* The internal structure stored in the weak references table.

* It maintains and stores

* a hash set of weak references pointing to an object.

* If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set

* is instead a small inline array.

*/

struct weak_entry_t {?

//weak變量指向的對象

?DisguisedPtr referent;

//選擇是用鏈表結構還是一個小數組存儲所有指向該對象的弱引用指針,weak_referrer_t是弱引用指針的內存地址

? ? union {

? ? ? ? struct {

? ? ? ? ? ? weak_referrer_t *referrers;

? ? ? ? ? ? uintptr_t? ? ? ? out_of_line_ness : 2;

? ? ? ? ? ? uintptr_t? ? ? ? num_refs : PTR_MINUS_2;

? ? ? ? ? ? uintptr_t? ? ? ? mask;

? ? ? ? ? ? uintptr_t? ? ? ? max_hash_displacement;

? ? ? ? };

? ? ? ? struct {

? ? ? ? ? ? // out_of_line_ness field is low bits of inline_referrers[1]

? ? ? ? ? ? weak_referrer_t? inline_referrers[WEAK_INLINE_COUNT];

? ? ? ? };

? ? };

? ? bool out_of_line() {

? ? ? ? return (out_of_line_ness == REFERRERS_OUT_OF_LINE);

? ? }

? ? weak_entry_t& operator=(const weak_entry_t& other) {

? ? ? ? memcpy(this, &other, sizeof(other));

? ? ? ? return *this;

? ? }

? ? weak_entry_t(objc_object *newReferent, objc_object **newReferrer)

? ? ? ? : referent(newReferent)

? ? {

? ? ? ? inline_referrers[0] = newReferrer;

? ? ? ? for (int i = 1; i < WEAK_INLINE_COUNT; i++) {

? ? ? ? ? ? inline_referrers[i] = nil;

? ? ? ? }

? ? }

};

看注釋可以知道,weak_entry_t用來存儲一個對象的weak引用的信息.weak_entry_t會保存一個HashSet,這個HashSet會存儲一個對象的弱引用的指針

所以現在可以得到結論了,weak變量初始化后保存在weak_entry_t這個結構體中,weak_entry_t會存儲weak指向的對象,同時weak_entry_t 的referrers會存儲這個對象的所有weak指針,weak_entry_t會儲存在weak_table_t這個結構體的weak_entries成員中,weak_table這個結構也是一個標準的hashTab的實現.weak_entry_t存儲在weak_table事,是以weak_entry_t的referent(即weak指針指向的對象)的hash值為key,weak_entry為value存儲在weak_table中的.

然后就是weak變量如何被釋放掉,在NSObject.mm文件中我可以找到如下函數:

/** * Destroys the relationship between a weak pointer?

and the object it is referencing in the internal weak ?table. If the weak pointer is not referencing anything, there is no need to edit the weak table. ? This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.)?

void objc_destroyWeak(id *location){?

?(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> ?(location, nil);

}

在注釋中寫的就是銷毀weak變量指針和對象的聯系,這個方法仍舊調用了stroreWeak,不過傳入的參數可以看到模板傳入的是DoHaveOld, DontHaveNew, DontCrashIfDeallocating ,實參傳入的是location(指針的內存地址), nil,這時我們在回頭看storeWeak方法,我們可以看到這種情況下storeWeak會調用weak_unregister_no_lock方法,我們繼續看weak_unregister_no_lock方法,在objc-weak.mm中我們可以看到weak_unregister_no_lock的實現:

**

* Unregister an already-registered weak reference.

* This is used when referrer's storage is about to go away, but referent

* isn't dead yet. (Otherwise, zeroing referrer later would be a

* bad memory access.)

* Does nothing if referent/referrer is not a currently active weak reference.

* Does not zero referrer.

*

* FIXME currently requires old referent value to be passed in (lame)

* FIXME unregistration should be automatic if referrer is collected

*

* @param weak_table The global weak table.

* @param referent The object.

* @param referrer The weak reference.

*/

void

weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,

? ? ? ? ? ? ? ? ? ? ? ? id *referrer_id)

{

//weak變量指向的對象

? ? objc_object *referent = (objc_object *)referent_id;

//weak變量的指針地址

? ? objc_object **referrer = (objc_object **)referrer_id;

? ? weak_entry_t *entry;

? ? if (!referent) return;

//通過weak_entry_t的referent找到weak_entry然后移除對應的referrer,

? ? if ((entry = weak_entry_for_referent(weak_table, referent))) {

? ? ? ? remove_referrer(entry, referrer);

? ? ? ? bool empty = true;

//判斷該對象的所有weak指針是否為空

? ? ? ? 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指針為空,則代表沒有任何weak指針指向這個對象,從weak_table中移除掉這個entry

? ? ? ? if (empty) {

? ? ? ? ? ? weak_entry_remove(weak_table, entry);

? ? ? ? }

? ? }

? ? // Do not set *referrer = nil. objc_storeWeak() requires that the

? ? // value not change.

}

從注釋中我們可以得到這個方法就是用來注銷已經存在的若引用指針,這個方法調用的時機是weak變量的referrer(weak變量的指針)將要消失的時候.

從代碼的實現中我們可以看出,方法內部首先移除的是weak_entry的referrer,如果weak_entry的referrers為空,再從weak_table中移除weak_entry.

首先我們要看remove_referrer這個方法:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)

{

//weak_entry->out_of_line()標記weak_entry->referrers的結構是數組還是hashTable

? ? if (! entry->out_of_line()) {

? ? ? ? for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {

//如果找到weak_entry的referrer,就將其置nil

? ? ? ? ? ? if (entry->inline_referrers[i] == old_referrer) {

? ? ? ? ? ? ? ? entry->inline_referrers[i] = nil;

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? _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;

? ? }

? ? size_t begin = w_hash_pointer(old_referrer) & (entry->mask);

? ? size_t index = begin;

? ? size_t hash_displacement = 0;

? ? while (entry->referrers[index] != old_referrer) {

? ? ? ? index = (index+1) & entry->mask;

? ? ? ? if (index == begin) bad_weak_table(entry);

? ? ? ? hash_displacement++;

? ? ? ? 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;

? ? ? ? }

? ? }

//找到weak變量的指針,將其置nil

? ? entry->referrers[index] = nil;

? ? entry->num_refs--;

}

從其代碼實現,我們就可以很明白的理解為什么weak變量釋放時weak指針會自動置nil了

接下來我們繼續看weak_entry_remove方法:


/**

* Remove entry from the zone's table of weak references.

*/

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)

{

? ? // remove entry

? ? if (entry->out_of_line()) free(entry->referrers);

? ? bzero(entry, sizeof(*entry));

? ? weak_table->num_entries--;

//根據weak_table的長度動態調整weak_table

? ? weak_compact_maybe(weak_table);

}

看注釋發現,這個方法就是把weak_entry從weak_table中移除.看代碼的實現,它釋放了weak_entry的referrent,同時將整個weak_entry內存全部置為0,之后將weak_table的長度減一.

以上就完成了將weak指針和其對象的關聯銷毀的全部過程.

當然還有一種情況就是weak指針指向的對象釋放后,是如何處理的.釋放對象的基本流程在NSObject.mm文件中也可以看到:

調用objc_release

若對象的引用計數為0,執行dealloc

在dealloc中,調用了_objc_rootDealloc函數

在_objc_rootDealloc中,調用了object_dispose函數

調用objc_destructInstance

最后調用objc_clear_deallocating

我們只需關心objc_clear_deallocating這個方法:

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)) {

? ? ? ? // Slow path for non-pointer isa with weak refs and/or side table data.

? ? ? ? clearDeallocating_slow();

? ? }

? ? assert(!sidetable_present());

}

從其實現代碼可以看到,針對被weak指向的對象,會調用clearDeallocating_slow方法,接下來可以看clearDeallocating_slow其實現代碼:

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_clear_no_lock方法,所以我們繼續看weak_clear_no_lock的實現,在objc-weak.mm中可以發現如下代碼,這個方法提供了在dealloc時,一次性釋放所有的weak變量

/**

* Called by dealloc; nils out all weak pointers that point to the

* provided object so that they can no longer be used.

*

* @param weak_table

* @param referent The object being deallocated.

*/


void??weak_clear_no_lock(weak_table_t *weak_table, id referent_id)

{

? ? objc_object *referent = (objc_object *)referent_id;

//從weak_table中找到weak_entry

? ? weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);

? ? if (entry == nil) {

? ? ? ? /// XXX shouldn't happen, but does with mismatched CF/objc

? ? ? ? //printf("XXX no entry for clear deallocating %p\n", referent);

? ? ? ? return;

? ? }

? ? // zero out references

? ? weak_referrer_t *referrers;

? ? size_t count;


//判斷weak_entry的referrers的結構

? ? if (entry->out_of_line()) {

? ? ? ? referrers = entry->referrers;

? ? ? ? count = TABLE_SIZE(entry);

? ? }

? ? else {

? ? ? ? referrers = entry->inline_referrers;

? ? ? ? count = WEAK_INLINE_COUNT;

? ? }


//釋放weak_entry的?referrers,將其置nil

? ? for (size_t i = 0; i < count; ++i) {

? ? ? ? objc_object **referrer = referrers[i];

? ? ? ? if (referrer) {

? ? ? ? ? ? if (*referrer == referent) {

? ? ? ? ? ? ? ? *referrer = nil;

? ? ? ? ? ? }

? ? ? ? ? ? else if (*referrer) {

? ? ? ? ? ? ? ? _objc_inform("__weak variable at %p holds %p instead of %p. "

? ? ? ? ? ? ? ? ? ? ? ? ? ? "This is probably incorrect use of "

? ? ? ? ? ? ? ? ? ? ? ? ? ? "objc_storeWeak() and objc_loadWeak(). "

? ? ? ? ? ? ? ? ? ? ? ? ? ? "Break on objc_weak_error to debug.\n",

? ? ? ? ? ? ? ? ? ? ? ? ? ? referrer, (void*)*referrer, (void*)referent);

? ? ? ? ? ? ? ? objc_weak_error();

? ? ? ? ? ? }

? ? ? ? }

? ? }

//從

? ? weak_entry_remove(weak_table, entry);

}

從注釋中可以看出,這個方法在dealloc的時候會被調用,當weak指針指向的對象被釋放的時候,會將改對象的所有weak指針全部置nil,并將該對象的weak_entry從weak_table中移除.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容