Weak-Strong-Dance真的安全吗?

作者: kuailejim | 来源:发表于2017-01-05 20:06 被阅读2872次

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

相关文章

  • Weak-Strong-Dance真的安全吗?

    绝大多数iOS开发者用过block,并且知道用 __weak 的方式去解决循环引用的问题。而进阶一些的开发者则了解...

  • 真的安全吗

    做为在化工厂上班的一名普通工人,被江苏盐城响水县的惊天事故震撼到了,近百人的伤亡,如同废墟一样的事故现场,让我感到...

  • 校园真的安全吗?

    《悲伤逆流成河》这部电影带给我最大的触动就是校园也不一定安全。 我们往往认为校园对于孩子来说是比较纯洁安...

  • iOS真的安全吗?

    最近看了很多关于手机安全方面的文章,突然感觉到现在移动平台(iOS,Android)上面都有很多不安全的地方,很多...

  • volatile真的安全吗

    关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,但是它并不容易被正确、完整地理解,以至于许多...

  • 安全感,真的安全吗?

    你所紧紧维护的,真的是让你感到安全的吗? 2017年8月15日星期二凌晨 心安:安全感,真的安全吗? 这是一期的圆...

  • 儿童安全绳真的安全吗?

    带着孩子出门在外 一不留神孩子走丢了该怎么办 家长们不免总有这样的担心 有一种“溜娃神器” 备受家长的喜欢 帕贝莎...

  • 安全感真的安全吗?

    前几天,有一位朋友找我聊天,跟我说,笑飞,我不想做了,想要辞职。我问她,原因是什么?她这样回答,在公司工作觉得心好...

  • 安全感,真的安全吗?

    男人不管在什么年龄中,他需要,有人就是爱慕她,仰视他就是男人安全感的一个来源,(罗子君给钱就行,查手机的防小...

  • 穿安全裤真的安全吗?

    前段时间跟友人们一起出去吃饭,那天朋友小白穿了一个比较短的灰色百褶裙。看她从座位上起身,我感觉那个裙子就很容易被人...

网友评论

  • Hi_CuteCoder:感觉楼主的问题应该偏向于block执行过程中,strongSelf还没赋值成功,self被释放后所引发的问题~
    weakSelf变为nil的时候,应该表示self本身已经是nil了吧,那么self.block相当于直接向nil发送消息,block本身应该不会被执行,strongSelf就是为了持有self防止在block执行过程中被释放而导致的非原子操作,引发异常,所以必须先strong一下,用新的strongSelf来强持有self,这样才能保证在多线程环境下,block中的内容会是原子操作,加上楼主的提示显然更保险~
    因为没有涉及KVO,所以一般都没引发问题,以后的格式感觉就变成这样吧:
    self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if(!strongSelf){
    //TODO
    }
    }
  • 有梦想de咸鱼:写得清晰明了,很好,我开始喜欢你了
    kuailejim:@有梦想de咸鱼 谢谢:smile:
  • 8d2b8086c81a:self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    [strongSelf copy];

    这句话很显然会导致内存泄漏。
    Tamp_:哦,没注意,括号内应该改成weakself
    8d2b8086c81a:@Tamp就要自由 你用了self
    Tamp_:为什么会内存泄漏呢,block并不会引用局部变量
  • 57be7734ecba:就算在执行前做了检查,这种做法也不是可重入的,还是有Crash的可能
  • hopestar90:说的太对了!
  • DrunkenMouse:前不久还看到说这的,我记得是为了防止执行到一半时block被意外释放导致的crash,而不是执行之前的。
    DrunkenMouse:@kuailejim 是我说的有误,我本意是block执行到一半时block所引用的self被意外释放导致的crash,但在Block执行之前self就有可能被释放。
    kuailejim:@DrunkenMouse 仔细看 无法保障也是执行时不被释放
  • Lion_Liu:精致分析,👍
  • chaoyang805:在 block 里进行 __strong 持有,主要是防止 block 执行到一半的时候 self 被释放吧。
    小怡情ifelse:@ibeforeold 写在一个控制器,点击返回dealloc当前控制器
    佛克斯:是的,再结合作者的内容,就可以做到万无一失了
    kuailejim:@chaoyang805 是的

本文标题:Weak-Strong-Dance真的安全吗?

本文链接:https://www.haomeiwen.com/subject/nowzvttx.html