美文网首页
你真的懂 weak strong dance 吗?

你真的懂 weak strong dance 吗?

作者: hanl001 | 来源:发表于2017-05-18 14:40 被阅读0次

    在阅读这篇文章之前,首先思考如下问题:

    1. 为什么 weak strong dance 能够避免循环引用?
    2. 为什么不直接使用weak?
    3. 使用 weak strong dance 的正确姿势?

    本文从 weak strong dance 的** 由来 用途 原理 扩展** 逐步分析解答上述问题,但不仅仅只是解答问题。


    由来

    在iOS开发中,无论objective-c还是swift都是采用引用计数来管理内存。而循环引用算是采用引用计数法管理内存中比较棘手的一个问题。在MRC时代,因为对象的引用计数是由程序猿自己来控制,优秀的程序员能够自如的把控对象之间的持有关系;到了ARC和swift中,由于编译器接手了内存管理的工作,为了方便程序员控制“对象之间的持有关系”,苹果于2011 WWDC Session #322中提出 weak strong dance ,官方示例代码如下

    - (void)dealloc
    {
      [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
    
    - (void)loadView
    {
      [super loadView];
    
      __weak TestViewController *wself = self;
      _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                    object:nil
                                                                     queue:nil
                                                                usingBlock:^(NSNotification *note) {
          TestViewController *sself = wself;
          [sself dismissModalViewControllerAnimated:YES];
      }];
    }
    

    用途

    在使用block时,因为block会捕获外部变量,并将其拷贝到block中。ARC 下这里的变量如果是指针变量,会将指针变量所指向的对象的引用计数加一。 因此如果不是对block有一定理解很容易发生循环引用,在这篇文章里有介绍会发生循环引用的情景。
    在RAC及swift中,为了避免block带来的循环引用,官方推荐 weak strong dance ,即 在block外部声明一个弱引用对象,在block内部声明一个局部变量强持有这个弱引用,通过使用新生成局部变量来避免循环引用。


    原理

    说到原理,得从block的内部结构说起:


    block内部结构

    其中invoke指向block的实现代码,variables保存block捕获到的外部变量。

    现在来分析引言中的示例代码:

    1. block 会将wself捕获到variables中,因为是weak修饰的,因此block不会对self进行强引用;同时 block 中的 invoke (函数指针)会指向 block 中的实现代码。
    2. 在执行block时(即调用invoke),TestViewController *sself = wself; 才会执行,如果执行这行代码时self还未释放,那么这里会将TestViewController实例的引用计数加一,防止在调用self期间self已经被释放。当这个方法执行完,ARC会自动将引用计数减一。
    3. 如果在执行block时,self已经释放,即wself被置为nil。那么 TestViewController *sself = wself; 执行时sself得到的也是一个nil,因此 [sself dismissModalViewControllerAnimated:YES]; 将不会被执行。
    4. 如果所有时候都和3中一样只是“不执行”,那该有多好。但是结果往往不如人意。不信? 测试代码如下:
    ///WeakTestObject.h
    typedef void(^test_block)(void);
    @interface WeakTestObject : NSObject
    /**
     *  <#summary#>
     */
    @property (copy,nonatomic) test_block block;
    @end
    
    ///ViewController.m
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        WeakTestObject *obj = [[WeakTestObject alloc]init];
        obj.block = ^{
            NSLog(@"execute block in obj");
        };
        __weak __typeof(obj) wObj = obj;
        test_block VcBlock = ^{
            WeakTestObject *sObj = wObj;
            sObj.block();
        };
        obj = nil;
        VcBlock();    
    }
    

    VcBlock 在执行之前,obj已经释放,导致执行 VcBlock 过程中 sObj 以及 sObj.block 均为nil。程序进而crash在 sObj.block(); 这里。 真实情况往往比这里的模拟代码复杂很多,可能会经过几次时间和空间的跨度;那么如何避免这种crash呢?

    两种处理:

    • 1 、对 sObj.block 进行判断
    test_block block = ^{
            WeakTestObject *sObj = wObj;
            if (sObj.block) {
                sObj.block();
            }
        };
    
    • 2、 对 sObj 进行判断
    test_block block = ^{
            WeakTestObject *sObj = wObj;
            if (sObj) {
                sObj.block();
            }
        };
    

    显然第二种处理更优。首先饿哦们没有必要对sObj的每一个舒心进行判断,其实 在使用sObj 时 ,往往也不是仅仅执行它的一个block属性,而且会涉及到block嵌套或其他各种坑爹情况,其次根据接口封闭原则我们也不应该过多去关心类的实现。

    最终 weak strong dance 的正确姿势如下:

    __weak __typeof(obj) wObj = obj;
        test_block block = ^{
            WeakTestObject *sObj = wObj;
            if (sObj) {
                /// do ...
            }
        };
    

    扩展

    1. RAC中的宏

    因为RAC中大量使用block语言,为了方便开发者RAC中定义了一对宏 @weakify() @strongify() ,对于这对宏的具体分析可阅读哀殿的 这篇文章 ,文中提到“Xcode 丢失了错误提示的能力”这一问题
    另外YYKit中也定义了类似的宏,同时避免了上述问题,如下

    #ifndef weakify
        #if DEBUG
            #if __has_feature(objc_arc)
            #define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
            #else
            #define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
            #endif
        #else
            #if __has_feature(objc_arc)
            #define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
            #else
            #define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
            #endif
        #endif
    #endif
    
    #ifndef strongify
        #if DEBUG
            #if __has_feature(objc_arc)
            #define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
            #else
            #define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
            #endif
        #else
            #if __has_feature(objc_arc)
            #define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
            #else
            #define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
            #endif
        #endif
    #endif
    

    这里补充另一个坑(请注意如下两种调用的区别)

    //1
    [self.viewModel.resignSubject subscribeNext:^(id x) {
            @strongify(self)
            [self.keyBoard keyboardDown];
        }];
    //2
    [self.viewModel.resignSubject subscribeNext:^(id x) {
            @strongify(self)
            [_keyBoard keyboardDown];
        }];
    

    在 1 中 self.keyBoard 中的self其实是被重定义的局部的“self”, 而我们通过 _keyBoard 调用的话,表面上虽然看起来连self都没有调用,更不会有什么问题了。但,第二种写法其实是存在很大隐患的,系统在“寻找” _keyBoard 这个实例对象时,是通过对 self 指针进行地址偏移得到的,在这里编译器可不会对这个self进行宏替换。

    在RAC源码中还有对另一个宏 @unsafeify() 的使用

    RACCompoundDisposable *selfDisposable = self.disposable;
        [selfDisposable addDisposable:otherDisposable];
    
        @unsafeify(otherDisposable);
    
        // If this subscription terminates, purge its disposable to avoid unbounded
        // memory growth.
        [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
            @strongify(otherDisposable);
            [selfDisposable removeDisposable:otherDisposable];
        }]];
    

    @unsafeify() 就是 __unsafe_unretained 在RAC中的宏,但是这种用法即使在RAC源码中出现的都极少,不过 __unsafe_unretained 对比 __weak 来说在性能会比较好。

    2. 接口设计原则

    首先,并不是涉及到block引用外部对象的问题都会带来循环引用;其次,如果是我们自己设计一个类的时候,应该尽量的避免可能产生循环引用的问题(例如执行完block后将其置nil),如果实在无法避免应该在接口里详细说明。
    例如:苹果框架中UIView封装的 animateWithDuration 方法、GCD等都
    不会带来循环引用(注:NSTimer方法可能会带来循环引用); 还有一些有名的三方框架例如 Masonry 也不会产生循环引用。

    3. swift 中的 weak strong dance
    testFunc(closure:{ [weak self] in
                if let strongSelf = self {
                    // do something
                    print(strongSelf)
                }
            })
    
    testFunc(closure:{ [weak self] in
                guard let strongSelf = self else {return}
                ///do something
                print(strongSelf)
            })
    

    上面是根据可选绑定方式得来的较常规的写法,还有如下这种方式,可避免另外显示生成strongSelf

    testFunc(closure:{ [weak self] in
                withExtendedLifetime(self, {
                   print(self ?? 0)
                })
            })
    

    只是经 withExtendedLifetime 处理后的self 变为了一个可选类型。

    相关文章

      网友评论

          本文标题:你真的懂 weak strong dance 吗?

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