你真的懂 weak strong dance 吗?

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

  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的内部结构说起:


你真的懂 weak strong dance 吗?_第1张图片
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 吗?)