@weakify & @strongify 原理分析

2017-06-20  本文已影响0人  大刘

RAC中使用@weakify和@strongify解决block使用过程中的弱引用和强引用的问题
我们在使用时比较简单:

 @weakify(self);
 [[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
    @strongify(self);
    ... ...
}];

RAC中的宏定义的比较深,关于这两个关键字,完全可以拿YYCategoriesMacro.h中的代码解析,来看一下YY中的代码:

/**
 Synthsize a weak or strong reference.
 
 Example:
    @weakify(self)
    [self doSomething^{
        @strongify(self)
        if (!self) return;
        ...
    }];
 */
#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

我们在此先不考虑是不是DEBUG环境,先把这个宏翻译过来并用普通代码书写,做个示例:

- (IBAction)buttonClick:(id)sender {
    self.redView = [[RedView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
    __weak __typeof__(self) weak_self = self;
    // __weak __typeof(&*self) weak_self = self; // OK
    // __weak typeof(self) weak_self = self; // OK
    // __weak id weak_self = self; // OK
    self.redView.callback = ^{
        // #define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
        __typeof__(self) self = weak_self;
        ViewController2 *controller = [[ViewController2 alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
    };
}

然后把我们的这个普通的常见写法做成宏就是上面宏的写法,只不过为了支持@,在宏中使用了一些技巧。但是为什么YY和RAC的宏里面都有看似多余的autoreleasepooltry{} ...代码呢?首先这个autoreleasepool和try的前面没有加@,所以我们在用的时候加上,形如:@weakify(self) & @strongify(self), 其次RAC官方给出的解释是:

Details about the choice of backing keyword:
The use of @try/@catch/@finally can cause the compiler to suppress return-type warnings. The use of @autoreleasepool {} is not optimized(优化) away by the compiler,resulting in superfluous(过多的) creation of autorelease pools.
Since neither option is perfect, and with no other alternatives, the compromise is to use @autorelease in DEBUG builds to maintain compiler analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary autorelease pools.

我们来分析一下,大致意思是说:使用@try/@catch/@finally 会导致编译器忽略返回值警告,而使用@autoreleasepool并不会被编译器优化,这样的话,使用@autoreleasepool{}会使程序创建过多的autorelease pool, 使用这两种方案都不是非常完美的,但是又没有其他方式,所以折中的做法是在DEBUG模式下使用@autorelease从而保持编译器的分析,在非DEBUG模式下使用@try/@catch从而避免插入过多的autorelease pool.

这段话还是比较好理解的,有个小困惑是 使用@try/@catch/@finally 会导致编译器忽略返回值警告,这句话的意思可以用一小段代码来解释:
我们来写一个block,用于判断传入的对象是不是Foo的实例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id foo = [[NSObject alloc] init];
        @autoreleasepool {}
        BOOL (^isEqualFoo)(id) = ^BOOL(id object) {
        }; // 编译器提示错误:Control reaches end of non-void block
        NSLog(@"%d", isEqualFoo(foo));
    }
    return 0;
}
图片.png

这是可以理解的,因为这是一个“返回值为BOOL, 跟一个id参数的block”,但是在block的内部却没有返回BOOL值,编译器提示错误,也就是说,我们必须返回一个BOOL,有趣的是,我们可以通过抛出一个异常让编译通过:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id foo = [[NSObject alloc] init];
        @autoreleasepool {}
        BOOL (^isEqualFoo)(id) = ^BOOL(id object) {
            @throw [NSException exceptionWithName:@"" reason:nil userInfo:nil]; // 编译器没有报错
        };
        NSLog(@"%d", isEqualFoo(foo));
    }
    return 0;
}

不仅如此,如果我们在这个block内部使用@try/@cath或@try/@finally语法,编译器同样不报错误,成功通过:

int main(int argc, const char * argv[]) {
   @autoreleasepool {
       id foo = [[NSObject alloc] init];
       @autoreleasepool {}
       BOOL (^isEqualFoo)(id) = ^BOOL(id object) {
           @try {} @finally {} // 编译通过,没毛病
           // return object == foo;
       };
       NSLog(@"%d", isEqualFoo(foo));
   }
   return 0;
}

这就是上面RAC解释说“The use of @try/@catch/@finally can cause the compiler to suppress return-type warnings.”的意思。
这种情况是Clang编译器底层做的处理,照老外的话说“weird”,在搜查了资料之后找到了"why"的原因:

参见这里:stackoverflow

上一篇下一篇

猜你喜欢

热点阅读