Weak-Strong-Dance真的安全嗎?

mo38794891 7年前發布 | 7K 次閱讀 iOS開發 移動開發

絕大多數iOS開發者用過 block ,并且知道用 __weak 的方式去解決循環引用的問題。而進階一些的開發者則了解 Weak-Strong-Dance ,那么什么是 Weak-Strong-Dance ?它能保證block執行是的 “安全” 嗎?

Weak-Strong-Dance

看看下面兩段代碼的區別,你就明白什么是 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 解決循環引用的前提下 ,在 block 內部用 __strong 持有對象,試圖解決 “在多線程下,可能weakSelf指向的對象會在 Block 執行前被廢棄,導致各種各樣的問題,比如說KVO,傳入nil可是會crash呢” ,如下代碼

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

此時,你可能會這樣認為, self 所指向對象的引用計數變成 2,即使主線程中的 self 因為超出作用于而釋放,對象的引用計數依然為 1,避免了對象的銷毀。

思維糾正

它真的能解決 在多線程下,可能 weakSelf 指向的對象會在 Block 執行前被廢棄而導致的問題嗎?

答案當然是 否定 的,讓我們來看看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改寫后的代碼,這里就只貼關鍵代碼了:

// @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

代碼很長,解釋下:

struct __TestBlock__test_block_impl_0 里頭,我們能看到 TestBlock *const __weak weakSelf; 這代表在 block 內部是以 弱引用 的方式捕獲 self 的,這沒毛病。重點來了,看這一段代表 block 具體實現的代碼塊

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"));
    }

這里可以看到如果此時外部廢棄了 self ,的確會導致 block 內部訪問成nil的情況。

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

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

看看clang改寫后會有什么區別:

  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!

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

所以持有 self 的行為是在 block 執行的時候才發生的!

回過頭來看看問題: 它真的能解決在多線程下,可能 weakSelf 指向的對象會在 Block 執行前被廢棄而導致的問題嗎?

在執行前就廢棄,到了執行的時候, weakSelf 已經是 nil 了,此時執行 __strong typeof(self) strongSelf = weakSelf; 根本沒意義吧。

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

總結

Weak-Strong-Dance 并不能保證 block所引用對象的釋放時機在執行之后 , 更安全的做法應該是在 block 內部使用 strongSelf 時進行 nil檢測 ,這樣可以避免上述情況。

 

來自:http://kuailejim.com/2017/01/05/Weak-Strong-Dance真的安全嗎?/

 

 本文由用戶 mo38794891 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!