微信号:cocoachinabbs

介绍:CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务.

Weak-Strong-Dance真的安全吗?

2018-02-09 22:00 kuailejim



绝大多数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检测,这样可以避免上述情况。


作者:kuailejim

链接:https://www.jianshu.com/p/737999a30544


 
Cocoa开发者社区 更多文章 iOS笔记 | Pointer is missing a nulla [译]用 LLDB 调试 Swift 代码 程序员以上帝视角解读“旅行青蛙”,你的呱真的在旅行嘛? 阿里数据iOS端启动速度优化的一些经验 使用 Swift 和 Vapor 构建区块链服务器
猜您喜欢 【重磅】Node Love Rust CSS in Action:样式优先级 find命令详解 你应该知道的浮点数基础知识