Weak-Strong-Dance真的安全嗎馋劈?

絕大多數(shù)iOS開(kāi)發(fā)者用過(guò)block攻锰,并且知道用 __weak 的方式去解決循環(huán)引用的問(wèn)題。而進(jìn)階一些的開(kāi)發(fā)者則了解Weak-Strong-Dance妓雾,那么什么是Weak-Strong-Dance娶吞?它能保證block執(zhí)行是的“安全”嗎?

Weak-Strong-Dance

看看下面兩段代碼的區(qū)別械姻,你就明白什么是Weak-Strong-Dance了妒蛇。

- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        [weakSelf copy];
    };
}
- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        [strongSelf copy];
    };
}

也就是在用 __weak 解決循環(huán)引用的前提下 ,在block內(nèi)部用 __strong 持有對(duì)象楷拳,試圖解決“在多線程下绣夺,可能weakSelf指向的對(duì)象會(huì)在 Block 執(zhí)行前被廢棄,導(dǎo)致各種各樣的問(wèn)題欢揖,比如說(shuō)KVO陶耍,傳入nil可是會(huì)crash呢”,如下代碼

__weak typeof(self) weakSelf = self;
self.handler = ^{
    typeof(weakSelf) strongSelf = weakSelf;
    [strongSelf.obserable removeObserver:strongSelf
                              forKeyPath:kObservableProperty];
};

此時(shí)她混,你可能會(huì)這樣認(rèn)為烈钞,self 所指向?qū)ο蟮囊糜?jì)數(shù)變成 2,即使主線程中的 self 因?yàn)槌鲎饔糜诙尫爬ぐ矗瑢?duì)象的引用計(jì)數(shù)依然為 1棵磷,避免了對(duì)象的銷(xiāo)毀。

思維糾正

它真的能解決在多線程下晋涣,可能 weakSelf 指向的對(duì)象會(huì)在 Block 執(zhí)行前被廢棄而導(dǎo)致的問(wèn)題嗎仪媒?
答案當(dāng)然是否定的,讓我們來(lái)看看demo:

不用Weak-Strong-Dance

#import "TestBlock.h"

@interface TestBlock ()

@property (nonatomic, strong) dispatch_block_t block;

@end

@implementation TestBlock

- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        [weakSelf copy];
    };
}

@end

看看用clang改寫(xiě)后的代碼,這里就只貼關(guān)鍵代碼了:

// @interface TestBlock ()

// @property (nonatomic, strong) dispatch_block_t block;

/* @end */


// @implementation TestBlock


  struct __TestBlock__test_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock__test_block_desc_0* Desc;
  TestBlock *const __weak weakSelf;
  __TestBlock__test_block_impl_0(void *fp, struct __TestBlock__test_block_desc_0 *desc, TestBlock *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) {
  TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy

        ((id (*)(id, SEL))(void *)objc_msgSend)((id)weakSelf, sel_registerName("copy"));
    }
static void __TestBlock__test_block_copy_0(struct __TestBlock__test_block_impl_0*dst, struct __TestBlock__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __TestBlock__test_block_dispose_0(struct __TestBlock__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __TestBlock__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __TestBlock__test_block_impl_0*, struct __TestBlock__test_block_impl_0*);
  void (*dispose)(struct __TestBlock__test_block_impl_0*);
} __TestBlock__test_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__test_block_impl_0), __TestBlock__test_block_copy_0, __TestBlock__test_block_dispose_0};

static void _I_TestBlock_test(TestBlock * self, SEL _cmd) {
    __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
    ((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__TestBlock__test_block_impl_0((void *)__TestBlock__test_block_func_0, &__TestBlock__test_block_desc_0_DATA, weakSelf, 570425344)));
}


static void(* _I_TestBlock_block(TestBlock * self, SEL _cmd) )(){ return (*(__strong dispatch_block_t *)((char *)self + OBJC_IVAR_$_TestBlock$_block)); }
static void _I_TestBlock_setBlock_(TestBlock * self, SEL _cmd, dispatch_block_t block) { (*(__strong dispatch_block_t *)((char *)self + OBJC_IVAR_$_TestBlock$_block)) = block; }
// @end

代碼很長(zhǎng)算吩,解釋下:
struct __TestBlock__test_block_impl_0里頭留凭,我們能看到TestBlock *const __weak weakSelf;這代表在 block 內(nèi)部是以弱引用的方式捕獲 self 的,這沒(méi)毛病偎巢。重點(diǎn)來(lái)了蔼夜,看這一段代表 block 具體實(shí)現(xiàn)的代碼塊

static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) {
  TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy

        ((id (*)(id, SEL))(void *)objc_msgSend)((id)weakSelf, sel_registerName("copy"));
    }

這里可以看到如果此時(shí)外部廢棄了self,的確會(huì)導(dǎo)致 block 內(nèi)部訪問(wèn)成nil的情況压昼。

那么如果用了Weak-Strong-Dance呢求冷?

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        [strongSelf copy];
    };

看看clang改寫(xiě)后會(huì)有什么區(qū)別:

  struct __TestBlock__test_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock__test_block_desc_0* Desc;
  TestBlock *const __weak weakSelf;
  __TestBlock__test_block_impl_0(void *fp, struct __TestBlock__test_block_desc_0 *desc, TestBlock *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestBlock__test_block_func_0(struct __TestBlock__test_block_impl_0 *__cself) {
  TestBlock *const __weak weakSelf = __cself->weakSelf; // bound by copy

        __attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;
        ((id (*)(id, SEL))(void *)objc_msgSend)((id)strongSelf, sel_registerName("copy"));
    }

holy shit!

區(qū)別在于在 block 內(nèi)多了這么一行代碼__attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;

所以持有 self 的行為是在 block 執(zhí)行的時(shí)候才發(fā)生的窍霞!

回過(guò)頭來(lái)看看問(wèn)題:它真的能解決在多線程下匠题,可能 weakSelf 指向的對(duì)象會(huì)在 Block 執(zhí)行前被廢棄而導(dǎo)致的問(wèn)題嗎?

在執(zhí)行前就廢棄但金,到了執(zhí)行的時(shí)候韭山,weakSelf 已經(jīng)是 nil 了,此時(shí)執(zhí)行 __strong typeof(self) strongSelf = weakSelf;根本沒(méi)意義吧冷溃。

所以在剛才KVO的例子中钱磅,該crash還是繼續(xù)crash吧。只要在執(zhí)行__strong typeof(self) strongSelf = weakSelf;前似枕,對(duì)象在其他線程被廢棄了盖淡,Weak-Strong-Dance不能幫上任何忙!

總結(jié)

Weak-Strong-Dance并不能保證 block所引用對(duì)象的釋放時(shí)機(jī)在執(zhí)行之后凿歼, 更安全的做法應(yīng)該是在 block 內(nèi)部使用 strongSelf 時(shí)進(jìn)行 nil檢測(cè)褪迟,這樣可以避免上述情況。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毅往,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子派近,更是在濱河造成了極大的恐慌攀唯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渴丸,死亡現(xiàn)場(chǎng)離奇詭異侯嘀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谱轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)戒幔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人土童,你說(shuō)我怎么就攤上這事诗茎。” “怎么了献汗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵敢订,是天一觀的道長(zhǎng)王污。 經(jīng)常有香客問(wèn)我,道長(zhǎng)楚午,這世上最難降的妖魔是什么昭齐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮矾柜,結(jié)果婚禮上阱驾,老公的妹妹穿的比我還像新娘。我一直安慰自己怪蔑,他們只是感情好里覆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著饮睬,像睡著了一般租谈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捆愁,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天割去,我揣著相機(jī)與錄音,去河邊找鬼昼丑。 笑死呻逆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菩帝。 我是一名探鬼主播咖城,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呼奢!你這毒婦竟也來(lái)了宜雀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤握础,失蹤者是張志新(化名)和其女友劉穎辐董,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體禀综,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡简烘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了定枷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孤澎。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖欠窒,靈堂內(nèi)的尸體忽然破棺而出覆旭,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布姐扮,位于F島的核電站絮供,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茶敏。R本人自食惡果不足惜壤靶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惊搏。 院中可真熱鬧贮乳,春花似錦、人聲如沸恬惯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酪耳。三九已至浓恳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碗暗,已是汗流浹背颈将。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留言疗,地道東北人晴圾。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像噪奄,于是被迫代替她去往敵國(guó)和親死姚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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