美文网首页
@weakify & @strongify 原理分析

@weakify & @strongify 原理分析

作者: 大刘 | 来源:发表于2017-06-20 10:09 被阅读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

    相关文章

      网友评论

          本文标题:@weakify & @strongify 原理分析

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