剖析RAC中的@weakify、@strongify

时间:2021-11-25 08:08:20

需要:pod 'YYKit'

 

在block语句块中,如果需引用self,而self对象中又持有block对象,就会造成循环引用循环引用(retain cycle),导致内存泄露,比如以下代码

self.block = ^{

        [self description];
    };

一般我们是这么解决的,使用一个__weal修饰的weakSelf变量指向self对象,在block中使用weakSelf:

 __weak typeof(self) weakSelf = self;
    self.block = ^{
[weakSelf description]; };

但是酱紫写,还是可能出问题,因为weakSelf是弱引用,而self一旦释放了,weakSelf可能为nil,还是举个栗子吧:
先定义一个TestObj对象,他的属性有一个block对象

@interface TestObj : NSObject@property (nonatomic, copy)void(^block)();@end@implementation TestObj- (void)dealloc {
    NSLog(@"%s",__func__);}- (instancetype)init {
    self = [super init];
    if (self) {

        __weak typeof(self) weakSelf = self;
        self.block = ^{
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [NSThread sleepForTimeInterval:1];
                NSLog(@"%@",weakSelf);
            });
        };
    }
    return self;
}

@end

执行testFunc方法,结果是打印的是(null),因为block里打印的方法是异步执行的,在 NSLog(@"%@",weakSelf);这句代码执行之前testFunc函数就结束,所以obj对象已经被release了。
怎么解决呢?所以再对weakSelf做一次 __strong就可以了:

__weak typeof(self) weakSelf = self;
        self.block = ^{

            __strong typeof(weakSelf) strongSelf = weakSelf;
            dispatch_async(dispatch_get_global_queue(0, 0), ^{

                [NSThread sleepForTimeInterval:1];
                NSLog(@"%@",strongSelf);
            });
        };
}

使用了__strongstrongSelf变量作用域结束之前,对weakSelf有一个引用,防止对象(self)提前被释放。而作用域一过,strongSelf不存在了,对象(self)也会被释放。

前面的写法虽然严谨了,也解决了问题了,但是作为喜欢偷懒的程序猿,会不会觉得很啰嗦?每次都要写那两条长长的__weak__strong,而且在block里用到的self的全部要改成strongSelf,假设把一段很多self的代码拷贝到block里,一个个改成strongSelf是不是很蛋疼?

只要在block外用了@weakify(self);然后再block里写@strongify(self);就可以了,@strongify(self);语句后的的self可以原封不动,好像很神奇,下面一起看看@weakify、@strongify 这两个神奇的宏最终替换了什么东西。
导入RAC的头文件,把上面的测试代码替换成RAC中用的@weakify(self);和@strongify(self), 分屏显示Xcode,让右侧的显示内容改为 preprocess“,就可以看到宏最终替换的结果。 

剖析RAC中的@weakify、@strongify

 

再比如:

     @weakify(self);
        _topView.block = ^(NSInteger tag) {
            
            @strongify(self);
            
            CGPoint point = CGPointMake(tag * SCREEN_WIDTH, self.scrollView.contentOffset.y);
            [self.scrollView setContentOffset:point animated:YES];
        };