從Objective-C源碼看weak

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

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

/** * 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變量會調(diào)用這個兩個方法其中一個進(jìn)行初始化.然后我們接著看后面的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變量的

下面是方法的具體實現(xiàn):

// 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代替.

首先會根據(jù)要存儲的對象去獲取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內(nèi)部有一個成員變量weak_table,不難猜出這個weak_table是用來存儲weak_entry的,接下來繼續(xù)看weak_table的結(jié)構(gòu)

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就是一個內(nèi)部包含一個weak_entries指針的結(jié)構(gòu)體,繼續(xù)看weak_entry_t的結(jié)構(gòu)

/**

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

//選擇是用鏈表結(jié)構(gòu)還是一個小數(shù)組存儲所有指向該對象的弱引用指針,weak_referrer_t是弱引用指針的內(nèi)存地址

? ? 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會存儲一個對象的弱引用的指針

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

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

/** * 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變量指針和對象的聯(lián)系,這個方法仍舊調(diào)用了stroreWeak,不過傳入的參數(shù)可以看到模板傳入的是DoHaveOld, DontHaveNew, DontCrashIfDeallocating ,實參傳入的是location(指針的內(nèi)存地址), nil,這時我們在回頭看storeWeak方法,我們可以看到這種情況下storeWeak會調(diào)用weak_unregister_no_lock方法,我們繼續(xù)看weak_unregister_no_lock方法,在objc-weak.mm中我們可以看到weak_unregister_no_lock的實現(xiàn):

**

* 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然后移除對應(yīng)的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.

}

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

從代碼的實現(xiàn)中我們可以看出,方法內(nèi)部首先移除的是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()標(biāo)記weak_entry->referrers的結(jié)構(gòu)是數(shù)組還是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--;

}

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

接下來我們繼續(xù)看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--;

//根據(jù)weak_table的長度動態(tài)調(diào)整weak_table

? ? weak_compact_maybe(weak_table);

}

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

以上就完成了將weak指針和其對象的關(guān)聯(lián)銷毀的全部過程.

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

調(diào)用objc_release

若對象的引用計數(shù)為0泽腮,執(zhí)行dealloc

在dealloc中烘嘱,調(diào)用了_objc_rootDealloc函數(shù)

在_objc_rootDealloc中次舌,調(diào)用了object_dispose函數(shù)

調(diào)用objc_destructInstance

最后調(diào)用objc_clear_deallocating

我們只需關(guān)心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());

}

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

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();

}

在實現(xiàn)中我們可以看到當(dāng)對象是若引用的時候會調(diào)用weak_clear_no_lock方法,所以我們繼續(xù)看weak_clear_no_lock的實現(xiàn),在objc-weak.mm中可以發(fā)現(xiàn)如下代碼,這個方法提供了在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的結(jié)構(gòu)

? ? 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的時候會被調(diào)用,當(dāng)weak指針指向的對象被釋放的時候,會將改對象的所有weak指針全部置nil,并將該對象的weak_entry從weak_table中移除.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市择吊,隨后出現(xiàn)的幾起案子芬迄,更是在濱河造成了極大的恐慌嘲碧,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椎瘟,死亡現(xiàn)場離奇詭異覆致,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肺蔚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門煌妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宣羊,你說我怎么就攤上這事璧诵。” “怎么了段只?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵腮猖,是天一觀的道長。 經(jīng)常有香客問我赞枕,道長澈缺,這世上最難降的妖魔是什么坪创? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮姐赡,結(jié)果婚禮上莱预,老公的妹妹穿的比我還像新娘。我一直安慰自己项滑,他們只是感情好依沮,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枪狂,像睡著了一般危喉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上州疾,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天辜限,我揣著相機(jī)與錄音,去河邊找鬼严蓖。 笑死薄嫡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颗胡。 我是一名探鬼主播毫深,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毒姨!你這毒婦竟也來了哑蔫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤手素,失蹤者是張志新(化名)和其女友劉穎鸳址,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泉懦,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡稿黍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了崩哩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巡球。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖邓嘹,靈堂內(nèi)的尸體忽然破棺而出酣栈,到底是詐尸還是另有隱情,我是刑警寧澤汹押,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布矿筝,位于F島的核電站,受9級特大地震影響棚贾,放射性物質(zhì)發(fā)生泄漏窖维。R本人自食惡果不足惜榆综,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铸史。 院中可真熱鬧鼻疮,春花似錦、人聲如沸琳轿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崭篡。三九已至挪哄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琉闪,已是汗流浹背中燥。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留塘偎,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓拿霉,卻偏偏與公主長得像吟秩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绽淘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容