為了解決 Block 造成的循環引用,iOS 開發過程中常常使用 @weakify 與 @strongify 來解決這個問題。下面就來看下 @weakify 與 @strongify 的實現原理。
准備知識
@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); }; }; }