Runtime學(xué)習(xí)-weak修飾符

1、__weak修飾符的使用案例

在開發(fā)的過程中,可能回遇到循環(huán)引用的問題蚁堤。所謂循環(huán)引用,當(dāng)對象A持有了對象B但狭,與此同時(shí)對象B同時(shí)也持有對象A時(shí)违寿,此時(shí)對象A銷毀需要對象B先銷毀,而對象B銷毀同樣需要對象A先銷毀熟空,就導(dǎo)致相互等待銷毀藤巢,此時(shí)對象A對象B的引用計(jì)數(shù)都不為0息罗,所以對象A掂咒,對象B此時(shí)都無法釋放。 從而導(dǎo)致了內(nèi)存泄漏迈喉。

解決循環(huán)引用最常用的方法就是使用__weak修飾符绍刮。

- (void)weakSelf{
    self.name = @"jack";
    //使用__weak修飾符后,若引用self挨摸,self的引用計(jì)數(shù)不會增加孩革。
    __weak typeof(self) weakSelf = self;
    void(^block)(void) = ^{
        NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
    };
    block();
}

2、weak引用的流程圖

weak斷點(diǎn).png

__weak打斷點(diǎn)得运,查看相關(guān)的匯編代碼膝蜈,可以看到接下來要跳轉(zhuǎn)的方法為objc_initWeak锅移。在源碼中查找該方法,該方法在NSObject.mm文件下饱搏。

方法:id objc_initWeak(id *location, id newObj)

  • id *location:弱應(yīng)用修飾的對象的位置
  • id newObj:被若引用的對象
    weak的相關(guān)底層方法:
    weak相關(guān)底層方法.png

    從這些方法可以看出非剃,弱引用是存儲在sideTable中。sideTable具體內(nèi)容這里不進(jìn)行說明推沸,只展示相關(guān)的數(shù)據(jù)結(jié)構(gòu)备绽,以便于后續(xù)的闡述。
    sideTable的結(jié)構(gòu)體.png

(1). 使用新的obj創(chuàng)建一個(gè)sideTableSideTable * newTable = &SideTables()[newObj];

*a鬓催、使用新值的指針肺素,獲得以 newObj 為索引所存儲的值地址。通過hash找到newObj所在的SideTable使用weak引者的地址---disguise_ptr(oldObj)宇驾,在SideTables中压怠,查找到改地址對應(yīng)的sideTable

(2). 使用原有的obj獲取到對應(yīng)的sideTableoldTable = &SideTables()[oldObj];

*a、使用weak引者的地址---disguise_ptr(oldObj)飞苇,在SideTables中,查找到改地址對應(yīng)的sideTable

(3). 使用weak_tablereferent來查找相關(guān)的entry:

static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)

*a蜗顽、通過weak_table獲取到weak_entriesweak_table->weak_entries
*b布卡、遍歷weak_entries,查找可以匹配referententry雇盖。[這里是通過對referent的哈希運(yùn)算忿等,并與weak_table->mask進(jìn)行進(jìn)行與操作,獲取到index(與mask的與操作崔挖,可以有效的防止越界操作)贸街,在index位置沒有匹配的referent,會自動向后匹配狸相。以weak_table->max_hash_displacement為匹配的邊界]

  /**********************************************************************/
  /*>>>>>計(jì)算出來entry在weak_table->weak_entries位置的index<<<<<*/
  /**********************************************************************/
  size_t begin = hash_pointer(referent) & weak_table->mask;
  size_t index = begin;
  size_t hash_displacement = 0;
  
  //weak_entries中取出index位置的referent薛匪,并不是要查詢的被引用obj,繼續(xù)循環(huán)
  while (weak_table->weak_entries[index].referent != referent) {
      //地址往后+1脓鹃,并計(jì)算出來新的index
      //這個(gè)與操作可以有效的防止index的越界
      index = (index+1) & weak_table->mask;
      
      //index和begin相等逸尖,即證明所有的entries已經(jīng)遍歷完成。
      if (index == begin) bad_weak_table(weak_table->weak_entries);
      
      //遍歷次數(shù)超過了max_hash_displacement瘸右,直接返回nil
      hash_displacement++;
      if (hash_displacement > weak_table->max_hash_displacement) {
          return nil;
      }
  }

*c娇跟、查找邏輯

weak_entry_for_referent.png

(4). 從entry的引用中,清除掉referrer

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

*a太颤、清除一個(gè)referrer苞俘,是遍歷entryreferrersentry->referrers[index]),匹配對應(yīng)的referrer
*b龄章、最重要的工作還是要查找referrer對應(yīng)的index吃谣。[這里是通過對referrer的哈希運(yùn)算乞封,并與entry->mask進(jìn)行進(jìn)行與操作,獲取到index(與mask的與操作基协,可以有效的防止越界操作)歌亲,在index位置沒有匹配的referrer,也是會進(jìn)行后向匹配澜驮。以entry->max_hash_displacement為匹配的邊界]

  /**********************************************************************/
  /*>>>>>計(jì)算出來referre在entry->referrers若引用位置的index<<<<<*/
  /**********************************************************************/
  //hash函數(shù)計(jì)算出來位置陷揪,
  size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
  size_t index = begin;
  size_t hash_displacement = 0;
  //從計(jì)算出來的位置開始查詢
  while (entry->referrers[index] != old_referrer) {
      index = (index+1) & entry->mask;
      if (index == begin) bad_weak_table(entry);
      hash_displacement++;
    
    //已經(jīng)超出了max_hash_displacement,報(bào)錯objc_weak_error杂穷,并返回
      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;
      }
  }

*c悍缠、找到了對應(yīng)的referrer以后,把查詢到的index位置的指向置為nil耐量,即從referrers中刪除了改referrer飞蚓。enry的num_refs--操作

    /**********************************************************************/
    /*>>>>>找到了需要清除的referrer的位置,進(jìn)行刪除操作廊蜒,并且把引用計(jì)數(shù)減1<<<<<*/
    /**********************************************************************/ 
 //找到了趴拧,從referre中刪除這個(gè)弱引用對象
    entry->referrers[index] = nil;
  //引用計(jì)數(shù)減1
    entry->num_refs--;

*d、刪除referrer操作

remove_referrer.png

(5). 刪除entry的操作山叮,即刪除entryreferrers中為空的entry

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

*a著榴、當(dāng)一個(gè)entry中,若引用對象的數(shù)量為0了屁倔,也就是referrers鏈表沒有數(shù)據(jù)脑又,這個(gè)entry就需要清除掉
*b、清除掉weak_table的一個(gè)entry后锐借,需要處理weak_table的num_entries问麸,進(jìn)行減1操作

  /**********************************************************************************/
  /*>>>>>清除掉weak_table->entries中為空的entry,并且weak_table->num_entries減1操作<<<<<*/
  /**********************************************************************************/ 
  // remove entry
  if (entry->out_of_line()) free(entry->referrers);
  bzero(entry, sizeof(*entry));

  weak_table->num_entries--;
  //壓weak_entryr中的entries的空間
/*
// Shrink if larger than 1024 buckets and at most 1/16 full.
  if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
      weak_resize(weak_table, old_size / 8);
      // leaves new table no more than 1/2 full
  }
*/
   weak_compact_maybe(weak_table);

*c钞翔、weak_table中清除entry

weak_entry_remove.png

(6). 創(chuàng)建entry并插入到一個(gè)weak_table的entries中:

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)

*a严卖、當(dāng)一個(gè)對象被弱引用,并且第一次被弱引用布轿,首先要創(chuàng)建一個(gè)引用對象和被引用的對象entry

  /*********************/
  /*>>>>>新建entry<<<<<*/
   /*********************/
      //新建entry妄田,
      weak_entry_t new_entry(referent, referrer);
      //判斷entries是否需要擴(kuò)容,需要的話驮捍,擴(kuò)容原來尺寸的2倍
      weak_grow_maybe(weak_table);

*b疟呐、把新建的entry插入到weak_table->weak_entries[和刪除entry時(shí)一樣的操作,第一步是要根據(jù)referent通過hash函數(shù)計(jì)算出來index(需要和weak_table->mask進(jìn)行與操作东且,防止越界)启具,查詢index處是否有數(shù)據(jù)。有數(shù)據(jù)的話珊泳,后向查詢下一個(gè)位置鲁冯,直到找到對應(yīng)的index拷沸。或者超出weak_table的最大存放數(shù)量]

    /***************************************************************/
    /*>>>>>計(jì)算新建的entry在weak_table->weak_entries的的位置index<<<<<*/
     /**************************************************************/
  //獲取到weak_entries
    weak_entry_t *weak_entries = weak_table->weak_entries;
  
        //函數(shù)函數(shù)計(jì)算存儲位置(理解為數(shù)組下標(biāo))
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    //查找到位index的位置薯演,如果有數(shù)據(jù)撞芍,就繼續(xù)查找下一個(gè)位置
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }

*c、計(jì)算出來所在的位置后跨扮,進(jìn)行插入操作序无,并且把weak_table->num_entries進(jìn)行加1操作

   //找到空位置index,把new_entery插入到index位置
    weak_entries[index] = *new_entry;
    //弱引用計(jì)數(shù)加1
    weak_table->num_entries++;

*d衡创、新建的entry插入到weak_table_entries

weak_entry_insert.png

(7). 已經(jīng)被弱引用的對象referent帝嗡,新加增弱引用操作。即entry->referres新增一個(gè)對象:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)

*a璃氢、在已經(jīng)被弱引用的前提下哟玷,再次被另一個(gè)對象進(jìn)行弱應(yīng)用,就要在entry->referres新增一個(gè)成員
*b一也、在插入新的referre之前巢寡,會先判斷referrers是不是需要進(jìn)行擴(kuò)容。如需要擴(kuò)容椰苟,擴(kuò)充為原來的兩倍

    /********************/
    /*>>>>>擴(kuò)容的操作<<<<<*/
    /********************/
  //referrers的帶下尺寸抑月,可以看出,如果需要擴(kuò)容的話尊剔,是按照old_size的兩倍進(jìn)行開辟空間的
   size_t new_size = old_size ? old_size * 2 : 8;

    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;
    
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));

*c、和其他的操作一樣菱皆,還時(shí)必須要計(jì)算出來referrer所在的位置index

    /******************************************************/
    /*>>>>>計(jì)算新的referrer所在的index<<<<<*/
   /*>>>>>w_hash_pointer(new_referrer) & (entry->mask);<<<<<*/
    /******************************************************/
//計(jì)算出來在entry中的index位置
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    
    //查找index位置是不是有數(shù)據(jù)须误。有的話。就往后加一個(gè)位置仇轻,繼續(xù)查詢
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }

*d京痢、將referrer插入到計(jì)算出來的index

  //取到index的地址,進(jìn)行referrer的存儲
  weak_referrer_t &ref = entry->referrers[index];
  ref = new_referrer;
  //弱引用指針個(gè)數(shù)加1
  entry->num_refs++;

*e篷店、將referrer插入到計(jì)算出來的index

append_referrer.png

3祭椰、對象銷毀時(shí),weak引用的處理

當(dāng)對象的引用計(jì)數(shù)為0的時(shí)候疲陕,或者調(diào)用了dealloc方法方淤,runtime會調(diào)用_objc_rootDealloc

對象銷毀的弱引用流程.png

  • 1蹄殃、一個(gè)對象銷毀的時(shí)候携茂,會查詢isa中的weakly_referenced參數(shù),如果是1的話诅岩,需要清理弱引用讳苦。
  • 2带膜、清理的時(shí)候,使用weak_table和referent查找到entry
  • 3鸳谜、遍歷entry中的referres膝藕,把里面的對象置為nil
  • 4、清理完成referrers后咐扭,刪除掉entry
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芭挽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子草描,更是在濱河造成了極大的恐慌览绿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穗慕,死亡現(xiàn)場離奇詭異饿敲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逛绵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門怀各,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人术浪,你說我怎么就攤上這事瓢对。” “怎么了胰苏?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵硕蛹,是天一觀的道長。 經(jīng)常有香客問我硕并,道長法焰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任倔毙,我火速辦了婚禮埃仪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陕赃。我一直安慰自己卵蛉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布么库。 她就那樣靜靜地躺著傻丝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诉儒。 梳的紋絲不亂的頭發(fā)上桑滩,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼运准。 笑死幌氮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胁澳。 我是一名探鬼主播该互,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼韭畸!你這毒婦竟也來了宇智?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤胰丁,失蹤者是張志新(化名)和其女友劉穎随橘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锦庸,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡机蔗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甘萧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萝嘁。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扬卷,靈堂內(nèi)的尸體忽然破棺而出牙言,到底是詐尸還是另有隱情,我是刑警寧澤怪得,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布咱枉,位于F島的核電站,受9級特大地震影響徒恋,放射性物質(zhì)發(fā)生泄漏蚕断。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一因谎、第九天 我趴在偏房一處隱蔽的房頂上張望基括。 院中可真熱鬧颜懊,春花似錦财岔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咸这,卻和暖如春夷恍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳维。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工酿雪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遏暴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓指黎,卻偏偏與公主長得像朋凉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子醋安,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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