iOS中的weak指針

ObjC runtime是如何實現(xiàn)weak指針的

  1. 用strong指針創(chuàng)建weak指針,系統(tǒng)會調(diào)用objc_initWeak()函數(shù),objc_initWeak中會判斷初始化表達(dá)式的右值是否為nil,如果為nil就把weak指針指向nil,否則調(diào)用storeWeak(),注意不是objc_storeWeak()

  2. 如果對weak指針賦值,則通過objc_storeWeak() 函數(shù),通過判斷weak指針是否為nil,右值是否為nil,然后調(diào)用storeWeak()

storeWeak()

聲明:
    template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
    static id storeWeak(id *location, objc_object *newObj);

之所以用template是為了優(yōu)化性能

操作:
    通過HaveOld判斷weak指針是否為nil,如果不是,則緩存舊值和通過&SideTables()[xxxObj];取出舊值所在的SideTables
    通過HaveNew判斷newObj是否為nil,如果不是則緩存newObj的SideTables

    對SideTables加鎖
        如果weak指針指向的值和舊值不一樣,則代表已經(jīng)處理了或者處于多線程data race中,會解鎖并從頭再來

    對newObj做處理,防止弱引用了的對象的isa指向空

    如果有舊值,調(diào)用weak_unregister_no_lock解除舊值所在weak table對weak指針的持有
        解除方式是通過查找舊值的SideTable的weak_table,遍歷找到并remove掉weak指針

    如果有newObj
        調(diào)用weak_register_no_lock綁定newObj     
            如果newObj使用了TaggedPointer優(yōu)化(比如NSNUmber,會指向常量所以不需要擔(dān)心內(nèi)存釋放),則直接返回
            判斷[newObj allowsWeakReference]是否為NO,如果為NO代表newObj在dealloc,此時如果CrashIfDeallocating為true,就會crash
            把weak指針綁定到newObj的weak table中,如果newObj沒有weak table則創(chuàng)建
        如果newObj還活著,weak_register_no_lock就會返回newObj,如果newObj是否isTaggedPointer,會調(diào)用newObj的setWeaklyReferenced_nolock()標(biāo)記一下
        把weak指針指向newObj
    
    對SideTables解鎖,返回newObj

SideTable是一個C++結(jié)構(gòu)體:
struct SideTable {
    spinlock_t slock;
        // 保證原子操作的自旋鎖

    RefcountMap refcnts;
        // 引用計數(shù)的 hash 表

        weak_table_t weak_table;
        // weak 引用全局 hash 表
}

struct weak_table_t {
    weak_entry_t *weak_entries;
        // 保存了所有指向指定對象的 weak 指針,weak_entry_t是節(jié)點的結(jié)構(gòu)體

    size_t    num_entries;
        // 存儲空間

    uintptr_t mask;
        // 參與判斷引用計數(shù)輔助量

    uintptr_t max_hash_displacement;
        // hash key 最大偏移值
};
  1. 使用一個weak指針創(chuàng)建另一個weak指針時,會調(diào)用objc_copyWeak()

  2. 讀取weak 指針時,會通過objc_loadWeakRetained(&weak指針)獲取其指向的對象(retain一次),然后把它加到autoreleasepool中

并且!!!訪問多少次weak 指針就會調(diào)用這么多次objc_loadWeakRetained和添加這么多次autoreleasepool,所以最好在block中拿到weak指針后,用一個__strong指針指向它,避免多余的操作,并且最好直接判斷一下是否為nil,是的話返回不做處理

  1. weak指針離開作用域時,會調(diào)用objc_destroyWeak() 函數(shù)把該指針從weak table中移除

  2. 當(dāng)此對象的引用計數(shù)為0的時候會 dealloc, dealloc的最后會調(diào)用object_dispose()函數(shù),觸發(fā)objc_clear_deallocating()函數(shù)在 weak 表中獲取對象的地址對應(yīng)的weak指針數(shù)組,從而設(shè)置為 nil,并且把相關(guān)的weak指針從weak table中移除

ps1:
當(dāng)一個OC對象的dealloc函數(shù)被觸發(fā)調(diào)用的時候,是不允許對其進(jìn)行弱引用的,如果在這時弱引用會引發(fā)crash,如:

-(void)dealloc{
    __weak __typeof(self) weakSelf = self;
}

ps2:
weak指針的objc_開頭的創(chuàng)建/修改函數(shù)都不是線程安全的,多線程下極低概率會導(dǎo)致data race crash

swift中的不橋接ObjC的話掂铐,weak實現(xiàn)方式會稍微有點不一樣:

這里大概講一下swift3之后的實現(xiàn):
弱指針基本等同于普通的指針。

弱引用指向?qū)ο髮嵗膕ide table地址

當(dāng)一個弱引用對象的deinit執(zhí)行后,對象并沒有被釋放双妨,且弱引用指針也沒有被賦nil。
當(dāng)弱引用執(zhí)行完deinit后叮阅,訪問弱引用對象刁品,則對象指針才會被賦nil,且目標(biāo)對象被釋放。
弱引用對象對于每一個弱引用會包含一個引用計數(shù)(unowned計數(shù)和strong計數(shù)為同一個)浩姥,且與強(qiáng)引用計數(shù)分開統(tǒng)計(但是統(tǒng)一管理的挑随,只有當(dāng)兩者都為0才會被釋放)。

這是因為Swift的計數(shù)方式默認(rèn)為InlineRefCounts勒叠,當(dāng)對象只包含strong或unowned引用時兜挨,使用InlineRefCounts進(jìn)行計數(shù)管理,當(dāng)對象擁有了weak引用眯分,InlineRefCounts會變?yōu)镾ideTableRefCounts

也就是說拌汇,swift對象的析構(gòu)和對象的釋放不一定是同時的,當(dāng)Swift對象的strong引用計數(shù)變?yōu)?但是weak計數(shù)大于0時弊决,對象會被析構(gòu)但是不會被釋放內(nèi)存

底層實現(xiàn)

系統(tǒng)在嘗試讀取weak指針時噪舀,會通過HeapObject *swift::swift_weakTakeStrong(WeakReference *ref)函數(shù)操作,該函數(shù)最終會來到HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) 函數(shù)

在這個函數(shù)中,會通過getNativeOrNull方法從對象的side table中查詢對象的計數(shù)飘诗,當(dāng)沒有strong引用時傅联,說明該對象已經(jīng)處于DEINITING狀態(tài),函數(shù)返回nullptr疚察,接著外層函數(shù)會賦空weak引用蒸走,weak計數(shù)減一,釋放剩余的對象內(nèi)存貌嫡,并返回nil

否則將調(diào)用weak的引用自減計數(shù)函數(shù)decrementWeak()比驻,再調(diào)用refCounts的decrementWeakShouldCleanUp()函數(shù)進(jìn)行位數(shù)操作该溯,當(dāng)weak、strong别惦、unowned計數(shù)都變?yōu)?時函數(shù)會返回true狈茉,這時候系統(tǒng)會清空weak指針對應(yīng)的side table,最后回到nativeTakeStrongFromBits調(diào)用tryRetain函數(shù)來嘗試獲取對象

完整實現(xiàn)大致如下:

HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) {
  auto side = bits.getNativeOrNull();
  if (side) {
    side->decrementWeak();
    return side->tryRetain();
  } else {
    return nullptr;
  }
}
void decrementWeak() {
  bool cleanup = refCounts.decrementWeakShouldCleanUp();
  if (!cleanup)
    return;
  assert(refCounts.getUnownedCount() == 0);
  delete this;
}

同時掸掸,添加weak對象會使對象的引用計數(shù)管理會從InlineRefCounts替換為SideTableRefCounts氯庆,這也會帶來一定的開銷,對于有性能要求的場景swift提供了unowned扰付,unowned的行為跟strong是一樣的堤撵,但不會使計數(shù)增加,代價是對象被釋放了的話羽莺,訪問unowned指針就是未定義的行為(相當(dāng)于ObjC中的訪問野指針)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末实昨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盐固,更是在濱河造成了極大的恐慌荒给,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刁卜,死亡現(xiàn)場離奇詭異志电,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛔趴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門挑辆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夺脾,你說我怎么就攤上這事≤约蹋” “怎么了咧叭?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烁竭。 經(jīng)常有香客問我菲茬,道長,這世上最難降的妖魔是什么派撕? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任婉弹,我火速辦了婚禮,結(jié)果婚禮上终吼,老公的妹妹穿的比我還像新娘镀赌。我一直安慰自己,他們只是感情好际跪,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布商佛。 她就那樣靜靜地躺著喉钢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪良姆。 梳的紋絲不亂的頭發(fā)上肠虽,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音玛追,去河邊找鬼税课。 笑死,一個胖子當(dāng)著我的面吹牛痊剖,可吹牛的內(nèi)容都是我干的韩玩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼邢笙,長吁一口氣:“原來是場噩夢啊……” “哼啸如!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氮惯,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤叮雳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妇汗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帘不,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年杨箭,在試婚紗的時候發(fā)現(xiàn)自己被綠了寞焙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡互婿,死狀恐怖捣郊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慈参,我是刑警寧澤呛牲,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站驮配,受9級特大地震影響娘扩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壮锻,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一琐旁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猜绣,春花似錦灰殴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擅羞。三九已至,卻和暖如春义图,著一層夾襖步出監(jiān)牢的瞬間减俏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工碱工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留娃承,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓怕篷,卻偏偏與公主長得像历筝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子廊谓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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