解讀objc源碼:weak的實(shí)現(xiàn)原理

一、weak的作用

這里的weak包括屬性關(guān)鍵字weak和__weak兩種:__weak用于修飾變量(variable),weak用于修飾屬性(property)。

1. weak是弱引用,用weak描述修飾或者所引用對(duì)象的計(jì)數(shù)不會(huì)加1
2. weak修飾的指針在引用的對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為nil,避免野指針
3. weak可以解決使用block引起的循環(huán)引用。

看到這里又想起了block和assign,復(fù)習(xí)一下這幾個(gè)的區(qū)別:

1.__block不管是ARC還是MRC模式下都可以使用,可以修飾對(duì)象,還可以修飾基本數(shù)據(jù)類型。
2.__weak只能在ARC模式下使用,也只能修飾對(duì)象(NSString),不能修飾基本數(shù)據(jù)類型(int)。assign用來修飾基本數(shù)據(jù)類型
3.__block對(duì)象可以在block中被重新賦值,__weak不可以。
4.__block對(duì)象在ARC下可能會(huì)導(dǎo)致循環(huán)引用,非ARC下會(huì)避免循環(huán)引用,__weak只在ARC下使用,可以避免循環(huán)引用。

OK,以上說的都沒有錯(cuò),大家也都會(huì)用,但是你知道是怎么實(shí)現(xiàn)的嗎


二、weak的源碼實(shí)現(xiàn)

1、先看下objc-weak.h文件里面的API
typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t { ... };
struct weak_table_t { ... };
/// Adds an (object, weak pointer) pair to the weak table.
id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                         id *referrer, bool crashIfDeallocating);
/// Removes an (object, weak pointer) pair from the weak table.
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
#if DEBUG
/// Returns true if an object is weakly referenced somewhere.
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
#endif
/// Called on object destruction. Sets all remaining weak pointers to nil.
void weak_clear_no_lock(weak_table_t *weak_table, id referent);

看起來很簡(jiǎn)單的結(jié)構(gòu)體和幾個(gè)方法:

  • weak_table_t:翻譯一下注釋上的說明,弱引用的哈希表table,對(duì)象id作為key,weak_entry_t結(jié)構(gòu)體作為value
  • weak_register_no_lock:向weak table里面添加一個(gè)object和它的weak引用指針
  • weak_unregister_no_lock:從weak table移除一個(gè)object和它的weak引用指針
  • weak_is_registered_no_lock:如果object有弱引用返回true
  • weak_clear_no_lock:object對(duì)象被銷毀,把所有指向它的弱引用指針全部置為nil

2、根據(jù)API看下.m里面的實(shí)現(xiàn)

2.1 weak_register_no_lock添加弱引用
源碼實(shí)現(xiàn):

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    // ensure that the referenced object is viable
    。。。這里省略判斷被引用的對(duì)象是否存在的代碼,只看關(guān)鍵部分
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}

解釋一下關(guān)鍵地方:

  1. if ((entry = weak_entry_for_referent(weak_table, referent))):判斷weak_table是否存在這個(gè)對(duì)象referent
  2. 如果table表中已經(jīng)存在對(duì)象referent,那么執(zhí)行append_referrer(entry, referrer);把referrer這個(gè)新的引用加入到referent已經(jīng)存在的引用列表中
  3. 如果不存在,執(zhí)行
    weak_entry_t new_entry(referent, referrer):給對(duì)象referent創(chuàng)建一個(gè)新的引用列表
    weak_grow_maybe(weak_table):weak_table增加內(nèi)存
    weak_entry_insert(weak_table, &new_entry):把referent的引用列表加入到weak_table中

2.2 weak_unregister_no_lock:移除弱引用
當(dāng)我們使用weak指針指向一個(gè)對(duì)象__weak typeof(self) = self的時(shí)候,會(huì)調(diào)weak_register_no_lock在weak table表中添加這個(gè)引用,但是移除引用什么時(shí)候會(huì)用呢,還是看一下源碼和注釋:

/** 
 * 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.
 */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    // referent是對(duì)象也就是weak table中的key
    objc_object *referent = (objc_object *)referent_id;
    //*referrer是指向這個(gè)對(duì)象的弱引用指針
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
    ///從weak_table中根據(jù)key(referent)找到entry(指向?qū)ο笕跻昧斜淼闹羔槪?    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        /// 從引用列表中移除這個(gè)引用referrer, remove_referrer這個(gè)方法會(huì)把這個(gè)referrer置為nil
        remove_referrer(entry, referrer);
        /// 之后遍歷對(duì)象referent的引用列表是不是空了
        bool empty = true;
        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;
                }
            }
        }
        if (empty) {
            /// 如果該對(duì)象的弱引用列表已經(jīng)empty,就把該對(duì)象從weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
}

這個(gè)應(yīng)該是這種情況下使用的,比如我們?cè)谝粋€(gè)方法里面這樣定義:

- (void)testWeakReferrer {
    __weak typeof(self) weakSelf = self;
    ...
    return;
}

weakSelf是方法的局部變量,當(dāng)方法執(zhí)行完的時(shí)候,局部變量weakSelf會(huì)被銷毀,這個(gè)時(shí)候系統(tǒng)應(yīng)該就會(huì)自動(dòng)執(zhí)行weak_unregister_no_lock方法,把weakSelf從self對(duì)象的弱引用列表中移除,并且把weakSelf置為nil。


2.3 weak_clear_no_lock清空所有的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_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    ...
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    ...//獲取列表中的數(shù)量
    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);
}

Called by dealloc,當(dāng)對(duì)象被銷毀的時(shí)候調(diào)用這個(gè)方法,在這個(gè)方法里面把所有指向這個(gè)對(duì)象的弱引用指針全部置為nil,最后從weak table中移除該對(duì)象和它的弱引用列表。

ok,這也就解釋了為什么weak指針會(huì)被自動(dòng)置為nil了

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

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