为什么要使用Weak-Strong-Dance

作者: 9fda4b908fad | 来源:发表于2017-03-26 20:41 被阅读119次

    我在之前分享了一下Block循环引用的实例应用场景链接地址,当时为了简要(其实是懒)仅仅只用了__ weak这个关键字进行说明,但是其实相对安全的做法并不是这样,相对安全的做法是什么呢?就是本篇所要讨论的Weak-Strong-Dance.

    什么是Weak-Strong-Dance

    平时我们在开发中经常使用__ weak来解决block循环引用的问题,也就是这样:

    __weak typeof(self) weakSelf = self;
    
    self.block = ^{
    
           NSLog(@"%@",weakSelf);
    };
    
    

    这种做法有一个弊端,就是你不知道self什么时候就释放了,上面的例子只是简单的打印了一下问题不大,但是如果你在开发中如果是因为使用这个变量而导致crash,岂不是得不偿失.比如说你把self加入到字典,或者执行self内部的block,此时就会引起crash,那此时Weak-Strong-Dance就起作用了.

    为了模拟crash这个场景,我用了以下这个例子,点击一个控制器的界面跳转进另外一个控制器界面,在这个控制器中,给自己的block赋值,然后在这个block中执行当前控制器的另外一个logBlock,在退回上个控制器的时候执行block,代码如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __weak typeof(self) weakSelf = self;
        
        self.logBlock = ^{
            
            NSLog(@"%@",weakSelf);
        };
        
        self.block = ^{
            
            weakSelf.logBlock();
        }; 
    }
    
    - (IBAction)back:(id)sender {
        self.block();
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    

    此时是没有问题的,没有循环引用,也没有crash,但是我让logBlock延时一段时间执行,也就是这样:

     self.block = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                weakSelf.logBlock();
            });        
    };
    

    分析:造成这种崩溃的原因是当退出当前控制器的时候,控制器正常释放,而延时执行block里面的代码的时候,控制器已经被释放了,再调用该控制器中的block引起了crash.

    此时我稍微改动一下代码,在block中加入如下一段代码:
    __strong typeof(self) strongSelf = weakSelf;
    也就是这样:

    self.block = ^{
            __strong typeof(self) strongSelf = weakSelf;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                strongSelf.logBlock();
            });
     };
    

    此时程序一切正常,这就是我们的Weak-Strong-Dance,为什么这么神奇呢?空口无凭,让我们通过clang命令来看看最后生成的c++代码:
    clang命令:

    clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations SencondViewController.m
    

    SencondViewController.m改成你想编译的文件名,这里只截取我们分析用的一些代码

    __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
    
        ((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setLogBlock:"), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_0((void *)__SencondViewController__viewDidLoad_block_func_0, &__SencondViewController__viewDidLoad_block_desc_0_DATA, weakSelf, 570425344)));
    
        ((void (*)(id, SEL, dispatch_block_t))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_2((void *)__SencondViewController__viewDidLoad_block_func_2, &__ SencondViewController__viewDidLoad_block_desc_2_DATA, weakSelf, 570425344)));
    
    

    这部分代码是 __ weak,以及给block赋值的代码

    可以看到我们在setBlock的时候构造了一个____ SencondViewController __viewDidLoad_block_impl_2结构体函数地址作为参数进行保存,等下调用block的时候就是根据这个地址去找这个结构体函数里面的 __ SencondViewController __viewDidLoad_block_func_2这个指针

    struct __SencondViewController__viewDidLoad_block_impl_2 {
      struct __block_impl impl;
      struct __SencondViewController__viewDidLoad_block_desc_2* Desc;
      SencondViewController *const __weak weakSelf;
    //构造结构体
      __SencondViewController__viewDidLoad_block_impl_2(void *fp, struct __SencondViewController__viewDidLoad_block_desc_2 *desc, SencondViewController *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    //这个函数就是我们执行block调用的函数
    static void __SencondViewController__viewDidLoad_block_func_2(struct __SencondViewController__viewDidLoad_block_impl_2 *__cself) {
      SencondViewController *const __weak weakSelf = __cself->weakSelf; // bound by copy
    
    
            __attribute__((objc_ownership(strong))) typeof(self) strongSelf = weakSelf;
            dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__SencondViewController__viewDidLoad_block_impl_1((void *)__SencondViewController__viewDidLoad_block_func_1, &__SencondViewController__viewDidLoad_block_desc_1_DATA, strongSelf, 570425344)));
    
        }
    

    这部分代码是保存block地址,以便后续调用

    static void _I_SencondViewController_back_(SencondViewController * self, SEL _cmd, __strong id sender) {
        ((dispatch_block_t (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("block"))();
        ((void (*)(id, SEL, BOOL, void (^ _Nullable __strong)()))(void *)objc_msgSend)((id)self, sel_registerName("dismissViewControllerAnimated:completion:"), ((bool)1), (void (^ _Nullable)())__null);
    }
    

    这是调用的代码

    更为详细的调用过程可以看一下我之前的一篇文章 (链接地址)

    通过观察以上代码可以看出,在给block赋值的时候我们捕获了一下_weakSelf并赋值给结构体的weakSelf变量,调用时候对block进行了一个强引用,此时weakSelf的引用计数器+1,就算此时退出当前页面weakSelf也不会马上释放,会等到延时操作执行完才会释放,所以我们的程序不会崩溃

    但是!!!这还不是最保险的,想想这样一个场景,如果此时我们没有马上对weakSelf进行一次强引用操作呢,也就是这么一个比较奇葩的场景:

    self.block = ^{
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                __strong typeof(self) strongSelf = weakSelf;
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    strongSelf.logBlock();
                });
            });
            
    };
    

    此时程序还是会crash掉,就是因为虽然此时你进行了一次强引用,但是weakSelf在你进行引用之前就已经被释放了,有兴趣的可以试下.

    总结

    Weak-Strong-Dance这种写法是为了防止我们在调用block时候,引用的对象已经被释放造成一些问题,这种写法可以比较完美的解决我们绝大部分循环引用的问题,但是考虑一些比较奇葩的地方,虽然利用__strong我们进行了强引用,但是还是建议使用之前进行一次非空判断,因为你不能保证你强引用的是不是nil
    本篇结束,如发现错误,欢迎指正

    相关文章

      网友评论

        本文标题:为什么要使用Weak-Strong-Dance

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