为了解决 Block 造成的循环引用,iOS 开发过程中常常使用 @weakify 与 @strongify 来解决这个问题。下面就来看下 @weakify 与 @strongify 的实现原理。

准备知识

宏参数(Arguments)的扩展

可变参数宏

宏定义中的重复副作用

宏定义里面为什么要加括号?

Block对变量的引用

@weakify 和 @strongify 的实现原理就是宏展开,阅读上面的准备知识可以更好的理解下面宏展开的过程。

@weakify、@strongify 替换后的结果

我们首先看 @weakify 与 @strongify 替换后的结果,假如我们有如下代码:

@interface ViewController ()

@property (nonatomic, copy) void (^testBlock1)(void);
@property (nonatomic, copy) void (^testBlock2)(void);

@end

@implementation ViewController

- (void)viewDidLoad {
    @weakify(self);
    self.testBlock1 = ^{
        @strongify(self);
        NSLog(@"%@", self);
    };
}

@end

因为 @weakify 与 @strongify 本质上是宏实现,在 Xcode 中选择菜单 Product -> Perform Action -> Preprocess "xx.m"(xx.m 就是文件名),就会看到最终替换的结果:

- (void)viewDidLoad {
    @autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self); // @weakify 替换结果
    self.testBlock1 = ^{
        @autoreleasepool {}  __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_; // @strongify 替换结果, block里面可以重定义self
                        ;
        NSLog(@"%@", self);
    };
}

可以看到,最终替换的结果其实很简单。最开始的 @ 符号被 autoreleasepool 给"吃掉",__attribute__((objc_ownership(weak))) 与 __attribute__((objc_ownership(strong))) 相当于 __weak 与 __strong,__typeof__ 等价于 typeof(关于两者的关系,参看

宏定义中的重复副作用)。那么整个替换结果就相当于:

@weakify(self); -> __weak typeof(self)  self_weak_ = self;

@strongify(self); -> __strong typeof(self)  self = self_weak_;

为什么 Bolock 里面可以重新定义 self

上面替换结果需要注意的第一个地方是,Block 里面可以重新定义 self。为了弄清楚原理,首先查看 Clang 文档对于 self 的解释:

可以看到,self 其实不是一个关键字,而只是一个参数变量。在 OC 方法中,self 作为第一个隐藏参数传递给相关的方法。假设有如下代码:

@implementation X

- (void)test {
   __weak typeof(self) weakSelf = self;
    self.testBlock1 = ^{
          __strong typeof(self)  self = weakSelf;
    };
}

@end

使用 clang -rewrite-objc(由于这里使用了 __weak 关键字,需要运行时支持,同时要开启 ARC,完整的命令为 clang -framework foundation  -rewrite-objc -fobjc-arc  -fobjc-runtime=macosx-11.7)查看 c++ 代码:

static void _I_X_test(X * self, SEL _cmd) { // test方法
   __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
 ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTestBlock1:"), ((void (*)())&__X__test_block_impl_0((void *)__X__test_block_func_0, &__X__test_block_desc_0_DATA, weakSelf, 570425344)));
}

虽然在 OC 里面 test 方法没有参数,但实际上有2个参数传给了它,第一个就是 self,另一个是方法名字符串 _cmd。因为这个原因,我们无法在 OC 方法里面重新定义 self,这样会报错。

接下来看 Block 里面的实现,同样查看 c++ 代码:

struct __X__test_block_impl_0 { // Block的实现
  struct __block_impl impl;
  struct __X__test_block_desc_0* Desc;
  X *const __weak weakSelf; // Block 持有了 weakSelf
  __X__test_block_impl_0(void *fp, struct __X__test_block_desc_0 *desc, X *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __X__test_block_func_0(struct __X__test_block_impl_0 *__cself) { // Block方法的实现
  X *const __weak weakSelf = __cself->weakSelf; // bound by copy

    __attribute__((objc_ownership(strong))) typeof(self) self = weakSelf;
 }

从 Block 的实现可以看到,Block 持有了 weakSelf。同时在 Block 的方法实现 __X__test_block_func_0 中只接收了一个参数,这个参数就是 Block,而不是像 OC 方法一样有 self 参数。正是因为这个原因,才可以在 Block 方法里面重新定义 self 变量。

为什么 typeof(self) 不会引起 Block 强引用

替换结果需要注意的第二个地方是,替换后 Block 里面仍然有 self 的出现,这个 self 会造成强引用吗?答案是不会。typeof(self) 可以看成是一个类型,也就是说替换后的结果实际上可以看成:

@weakify(self); -> __weak ViewController * self_weak_ = self;

@strongify(self); -> __strong ViewController *self = self_weak_;

同时,上面类 X 的 c++ 源码也可以看到,Block 确实没有对 self 造成强引用。

@weakify 的实现原理

我们接下来一层一层将 weakify 的宏展开,看下它的实现原理。

#define weakify(...) \
    rac_keywordify \
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

可以看到 weakify 宏接收一个可变参数(可变参数宏参看可变参数宏)。

rac_keywordify 定义如下:

#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

可以看到 rac_keywordify 在 DEBUG 模式下是 autoreleasepool {},而在 release 模式下是 try..catch语句。这两个都可以"吃掉" @weakify 前面的 '@' 符号,之所以要进行区别,是因为如果在 DEBUG 模式下使用 try...catch,若果碰到 Block 有返回值,会抑制 Xcode 对返回值检测的警告。比如:

@weakify(self);
self.block = ^int {
  @strongify(self);
  NSLog(@"123");
}

上面代码 Block 应该返回一个 int 类型,但是实际上没有返回。因为 try..catch 的存在,Xcode 不会产生报错或者警告,这对 DEBUG 模式下不友好。而在 release 模式下,空的 try...catch 会被编译器优化移除,而空的自动释放池不会被优化,对性能会有影响。由于这个原因,在 DEBUG 模式下使用了 autoreleasepool {},而 release 模式下使用 try...catch。

对于 @weakify(self),经过第一层宏展开(宏参数展开原理参看宏参数(Arguments)的扩展),结果如下:

@autoreleasepool {} metamacro_foreach_cxt(rac_weakify_,, __weak, self)

接下来对宏 metamacro_foreach_cxt 进行展开,首先看它的定义:

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

这个宏定义里面有一个宏 metamacro_argcount,它用来计算可变参数的个数,原理后面有讲,这里它扩展之后值为1。

metamacro_foreach_cxt 扩展后的结果为:

metamacro_concat(metamacro_foreach_cxt, 1)(rac_weakify_, ,__weak, self)

接下来对宏 metamacro_concat 进行扩展,它的定义为:

#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)


#define metamacro_concat_(A, B) A ## B

可以看到 metamacro_concat 就是一个字符串的连接操作,整个宏的扩展结果为:

metamacro_foreach_cxt1(rac_weakify_, ,__weak, self)

接下来看宏 metamacro_foreach_cxt1 的定义,定义为:

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

可以看到对于只有1个参数的情形,SEP 参数被忽略。整个宏的扩展结果为:

rac_weakify_(0, __weak, self)

宏 rac_weakify_ 的定义如下:

#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

宏 metamacro_concat 依然是字符串连接操作,最后扩展出来的结果就是:

__weak typeof(self) self_weak_ = (self)

@strongify 的实现原理

同样将宏 strongify 一层一层展开,首先看它的定义:

#define strongify(...) \
    rac_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

定义里面 rac_keywordify 宏和 weakify 一样,_Pragma 是 C99 引入,等价于 #pragma。将整个宏展开之后结果为:

@autoreleasepool {} metamacro_foreach(rac_strongify_, ,self)

接下来展开宏 metamacro_foreach,它的定义为:

#define metamacro_foreach(MACRO, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

展开结果为:

metamacro_foreach_cxt(metamacro_foreach_iter, , rac_strongify_, self)

宏 metamacro_foreach_cxt 展开的流程和 weakify 中讲解的一样,这里直接给出展开结果:

metamacro_foreach_cxt1(metamacro_foreach_iter, ,rac_strongify_, self)

宏 metamacro_foreach_cxt1 展开的流程也和 weakify 中讲解的一样,展开结果为:

metamacro_foreach_iter(0, rac_strongify_, self)

宏 metamacro_foreach_iter 定义为:

#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)

展开结果为:

rac_strongify_(0, self)

宏 rac_strongify_ 定义为:

#define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

展开结果为:

 __strong __typeof__(self) self = self_weak_;

计算可变参数个数的宏 metamacro_argcount

宏 metamacro_argcount 用来计算可变参数的个数,首先看它的定义:

#define metamacro_argcount(...) \
        metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

如果我们向 metamacro_argcount 宏传递了3个参数 arg1,arg2,arg3(也就是@weakify(arg1, arg2, arg3)),那么扩展后的结果为:

 metamacro_at(20, arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

宏 metamacro_at 的定义如下:

#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)

扩展后的结果为:

metamacro_concat(metamacro_at, 20)(arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

宏 metacro_concat 就是字符串连接操作,扩展后的结果为:

metamacro_at20(arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

宏 metamacro_at20 定义为:

#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)

这里比较重要,我们先将传递给 metamacro_at20 的实参与形参做一个对照:

arg1 arg2  arg3  20  19  18  17  16  15  14   13   12   11   10    9    8    7    6    5    4  3  2  1   // 实参
_0   _1   _2     _3  _4  _5  _6  _7  _8  _9  _10  _11  _12  _13  _14  _15  _16  _17  _18  _19            //  形参

通过对照发现,实参总共传递了23个,而形参总共有20个,因此实参比形参多的部分都是可变参数。也就是说,__VA_ARGS__的值为3, 2, 1,所以扩展后的结果为:

metamacro_head(3, 2, 1)

宏 metamacro_head 定义为:

#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)

#define metamacro_head_(FIRST, ...) FIRST

扩展之后,FIRST 就是3,刚好是传入的可变参数的个数。

通过分析发现,计算的原理其实就是利用宏 metamacro_at20 实参与形参的个数差。如果传递给宏 metamacro_at20 的实参个数是4个,那么实参与形参的对比为:

arg1 arg2  arg3  arg4 20  19  18  17  16  15  14   13   12   11   10    9    8    7    6    5    4  3  2  1   // 实参
_0   _1   _2     _3   _4  _5  _6  _7  _8  _9  _10  _11  _12  _13  _14  _15  _16  _17  _18  _19            //  形参

这样扩展之后的结果就是4。

如果传递的参数个数超过了20个,那就计算不出来了:

// 传入20个参数
arg1 arg2  arg3  arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 arg20 20  19  18  17  16  15  14   13   12   11   10    9    8    7    6    5    4  3  2  1   // 实参
_0   _1   _2     _3   _4    _5  _6   _7   _8    _9   _10    _11   _12   _13   _14  _15    _16   _17   _18   _19            //  形参


// 传入21个参数
arg1 arg2  arg3  arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 arg20  arg21 20  19  18  17  16  15  14   13   12   11   10    9    8    7    6    5    4  3  2  1   // 实参
_0   _1   _2     _3   _4    _5  _6   _7   _8    _9   _10    _11   _12   _13   _14  _15    _16   _17   _18   _19            //  形参

从上面可以看到,当传入20个参数,扩展之后结果是20;当传入21个时,扩展的结果是 arg21,就不对了。

多参数扩展

假如传递2个参数,也就是 @weakify(arg1, arg2) 与 @strongify(arg1, arg2),整个扩展过程中,差别就是宏 metamacro_foreach_cxt 的扩展。当参数个数为2时,metamacro_foreach_cxt 的扩展结果为宏 metamacro_foreach_cxt2。看一下宏 metamacro_foreach_cxt2 的定义:

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)

可以看到宏 metamacro_foreach_cxt2 首先调用 metamacro_foreach_cxt1,SEP 参数为空, 然后扩展 rac_weakify_(1, __weak, arg2)它的扩展结果为:

__weak __typeof__(arg1) arg1_weak_ = (arg1);
__weak __typeof__(arg2) arg2_weak_ = (arg2);

相应的 @strongify(arg1, arg2) 的扩展结果为:

__strong __typeof__(arg1) arg1 = arg1_weak_;
__strong __typeof__(arg2) arg2 = arg2_weak_;

由于 @weakify 与 @strongify 最多可以传递20个参数,所以宏 metamacro_foreach_cxt 有20个定义,从 metamacro_foreach_cxt1 到 metamacro_foreach_cxt20。每一个定义都是下一个 调用上一个 。比如宏 metamacro_foreach_cxt8 就是调用 metamacro_foreach_cxt7 实现:

#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    SEP \
    MACRO(7, CONTEXT, _7)

嵌套 Block 的情形

下面来看一下如果 Block 有嵌套,那么对于内层的 Block,还需要 @weakify 与 @strongify 对吗?

首先来看下,如果内层 Block 也使用 @weakify 与 @strongify 的情形:

- (void)viewDidLoad {
    @weakify(self);
    self.testBlock1 = ^{
        @strongify(self);
        NSLog(@"%@", self);
        @weakify(self);
        self.testBlock2 = ^{
            @strongify(self);
            NSLog(@"%@", self);
        };
    };
}

使用 Xcode 查看宏展开的结果:

- (void)viewDidLoad {
    @autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
    self.testBlock1 = ^{
        @autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
        NSLog(@"%@", self);
        @autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
        self.testBlock2 = ^{
            @autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
            NSLog(@"%@", self);
        };
    };
}

可以看到对于嵌套 Block,内层 Block 是不需要 @weakify的,只需要写 @strongify就可以。因为内层 Block 的@weakify 只是重新定义了 self_weak_。

下面是内层 Block 不写 @weakify 的情形:

- (void)viewDidLoad {
    @weakify(self);
    self.testBlock1 = ^{
        @strongify(self);
        NSLog(@"%@", self);
        self.testBlock2 = ^{
            @strongify(self);
            NSLog(@"%@", self);
        };
    };
}

用 Xcode 展开后的结果为:

- (void)viewDidLoad {
    @autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
    self.testBlock1 = ^{
        @autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
        NSLog(@"%@", self);
        self.testBlock2 = ^{
            @autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
            NSLog(@"%@", self);
        };
    };
}
03-20 18:22