@weakify 與 @strongify 實現原理

chaoguo1234發表於2022-03-20

為了解決 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 的解釋:

The self parameter variable of an non-init Objective-C method is considered externally-retained by the implementation...

可以看到,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);
        };
    };
}

 

相關文章