GCD 捕獲 self 是否會造成內(nèi)存泄漏婉支?

關(guān)于 GCD 的 block 捕獲 self 是否造成循環(huán)引用的問題,網(wǎng)上是爭論不休痢毒,在 iOS 的面試中更是頻繁出現(xiàn)送矩。我們從 YYKit 里面的一個?Issue

出發(fā),來探索一下 GCD 跟 self 之間是否會造成循環(huán)引用的問題哪替。

該 Issue 起源于 YYKit 中的一段代碼:

- (void)_trimInBackground {

__weak typeof(self) _self = self;

dispatch_async(_queue, ^{

__strong typeof(_self) self = _self;

/* 此處省略一萬字 **/

});

}


可以看到栋荸,YY 大神在 GCD 中,為了避免循環(huán)引用凭舶,使用了 strong-weak dance晌块,但是網(wǎng)友在該 Issue 中提出,蘋果的 dispatch_async 函數(shù)在 block 任務(wù)執(zhí)行完成后會將該 block 進(jìn)行 Block_release帅霜,并不會造成循環(huán)引用匆背,此處用 strong-weak dance 反而可能造成 block 執(zhí)行前 self 就已經(jīng)被釋放。

而 YY 大神的觀點(diǎn)則認(rèn)為身冀,由于self 持有一個 _queue 變量靠汁,而 _queue 會持有該 block蜂大,此時在 block 內(nèi)直接捕獲 self 則會造成循環(huán)引用。(self->_queue->block->self)

然而這樣真的會造成內(nèi)存泄漏嗎蝶怔?

探索

我們創(chuàng)建一個簡單的 Demo,代碼如下:

@interface ViewController2 ()

@property (nonatomic, strong) dispatch_queue_t queue;

@end

@implementation ViewController2

- (void)viewDidLoad {

[super viewDidLoad];

self.queue = dispatch_queue_create("com.mayc.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(self.queue, ^{

[self test];

});

}

- (void)test {

NSLog(@"test");

}

- (void)dealloc {

NSLog(@"dealloc");

}

@end


在 Demo 里兄墅,ViewController2 持有一個 queue 變量踢星,dispatch_async 的 block 中捕獲了 self。我們打開一個?ViewController2 的頁面隙咸,然后關(guān)掉沐悦;如果?dispatch_async 強(qiáng)捕獲 self 會造成內(nèi)存泄漏,那么?ViewController2 的 dealloc 方法必然是不會執(zhí)行的五督。 執(zhí)行結(jié)果如下:

2020-03-11 15:36:35.352789+0800 MCDemo[83661:22062265] test

2020-03-11 15:36:36.922477+0800 MCDemo[83661:22062108] dealloc


可以看到?ViewController2 被正常釋放了藏否,也就是說?并不會造成內(nèi)存泄漏

源碼分析

源碼面前無秘密充包,我們看一下 dispatch_async 的源碼:

void dispatch_async(dispatch_queue_t dq, dispatch_block_t work) {

dispatch_continuation_t dc = _dispatch_continuation_alloc();

uintptr_t dc_flags = DC_FLAG_CONSUME;

dispatch_qos_t qos;

// 將work(也就是我們傳進(jìn)來的任務(wù) block)封裝成 dispatch_continuation_t

qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);

_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);

}


可以看到副签,dispatch_async 傳入的 block 最終會與其他參數(shù)封裝成?dispatch_continuation_t,我們重點(diǎn)看一下這塊封裝的代碼(以下代碼有所精簡):

static inline dispatch_qos_t

_dispatch_continuation_init(dispatch_continuation_t dc,

dispatch_queue_class_t dqu, dispatch_block_t work,

dispatch_block_flags_t flags, uintptr_t dc_flags)

{

// 拷貝block

void *ctxt = _dispatch_Block_copy(work);

dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;

dispatch_function_t func = _dispatch_Block_invoke(work);

if (dc_flags & DC_FLAG_CONSUME) {

// 顧名思義基矮,執(zhí)行block淆储,然后釋放。

func = _dispatch_call_block_and_release;

}

return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);

}

// _dispatch_call_block_and_release的源碼如下

void _dispatch_call_block_and_release(void *block)

{

void (^b)(void) = block;

b(); // 執(zhí)行

Block_release(b); // 釋放

}


可以看到正如 Apple 文檔所說家浇,dispatch_async 會在 block 執(zhí)行完成后將其釋放本砰。因此?_self->?queue->block->self

這個循環(huán)引用只是暫時的(block 執(zhí)行完成后被釋放,打斷了循環(huán)引用)钢悲。

刨根問底

dispatch_sync

既然 dispatch_async 的 block 捕獲 self 不會造成循環(huán)引用点额,那么換成 dispatch_sync 會怎么樣呢?

self.queue = dispatch_queue_create("com.mayc.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(self.queue, ^{

[self test];

});


其實(shí) dispatch_sync 也不會有問題莺琳。我們把剛剛 Demo 中的 dispatch_async 換成 dispatch_sync 还棱,可以看到也未造成內(nèi)存泄漏。

2020-03-11 17:05:18.840834+0800 MCDemo[5437:69508] test

2020-03-11 17:05:20.419588+0800 MCDemo[5437:68626] dealloc


不過?dispatch_sync 不會造成?_self->?queue->block->self

循環(huán)引用的原因跟?dispatch_async 有所不同芦昔,不是因?yàn)閳?zhí)行完成后被 release诱贿,我們看一下官方關(guān)于?dispatch_sync 的文檔有段說明:

Unlike with?dispatch_async

, no retain is performed on the target queue. Because calls to this function are synchronous, it “borrows” the reference of the caller. Moreover, no?Block_copy

is performed on the block.

大致意思是說,queue 不會對 block 進(jìn)行持有咕缎,也不會進(jìn)行 Block_copy 操作珠十。既然 queue -> block 這一層引用不存在,自然也?不會造成循環(huán)引用

凭豪。

dispatch_after 等其他 GCD api

我們在 dispatch_after焙蹭、dispatch_group_async 的官方文檔里面也看可以到和 dispatch_async 類似的話:

_This function performs a?Block_copy

and?Block_release

on behalf of the caller.?_

可以看到這些 GCD api 的方法也都做了 release 處理,因此其他的這些也不會因?yàn)椴东@ self 而造成循環(huán)引用嫂伞。

拓展

既然就算 self 持有 queue 也不會造成 GCD 的循環(huán)引用孔厉,那如果是 self 直接持有 GCD 的 block 呢拯钻?

dispatch_queue_t queue = dispatch_queue_create("com.mayc.concurrent", DISPATCH_QUEUE_CONCURRENT);

self.block = ^{

[self test];

};

dispatch_async(queue, self.block);

emm…如果非要這樣的話,肯定是會內(nèi)存泄漏的….這是因?yàn)?block 被 self 直接持有撰豺,同時在 gcd 中進(jìn)行了一次 Block_copy 操作粪般,引用計數(shù)器為 2。block 任務(wù)執(zhí)行完成后進(jìn)行 Block_release污桦,此時引用計數(shù)器為1 亩歹,這種情況下 block 不會被清理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凡橱,一起剝皮案震驚了整個濱河市小作,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稼钩,老刑警劉巖顾稀,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坝撑,居然都是意外死亡静秆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門绍载,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诡宗,“玉大人,你說我怎么就攤上這事击儡∷郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵阳谍,是天一觀的道長蛀柴。 經(jīng)常有香客問我,道長矫夯,這世上最難降的妖魔是什么鸽疾? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮训貌,結(jié)果婚禮上制肮,老公的妹妹穿的比我還像新娘。我一直安慰自己递沪,他們只是感情好豺鼻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著款慨,像睡著了一般儒飒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檩奠,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天桩了,我揣著相機(jī)與錄音附帽,去河邊找鬼。 笑死井誉,一個胖子當(dāng)著我的面吹牛蕉扮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播送悔,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慢显,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了欠啤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屋灌,失蹤者是張志新(化名)和其女友劉穎洁段,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體共郭,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祠丝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了除嘹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片写半。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尉咕,靈堂內(nèi)的尸體忽然破棺而出叠蝇,到底是詐尸還是另有隱情,我是刑警寧澤年缎,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布悔捶,位于F島的核電站,受9級特大地震影響单芜,放射性物質(zhì)發(fā)生泄漏蜕该。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一洲鸠、第九天 我趴在偏房一處隱蔽的房頂上張望堂淡。 院中可真熱鬧,春花似錦扒腕、人聲如沸绢淀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽更啄。三九已至,卻和暖如春居灯,著一層夾襖步出監(jiān)牢的瞬間祭务,已是汗流浹背内狗。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留义锥,地道東北人柳沙。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像拌倍,于是被迫代替她去往敵國和親赂鲤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355