REACTIVE COCOA中的@WEAKIFY、@STRONGIFY是如何裝逼的

0.很長的前言

在block語句塊中,如果需引用self,而self對象中又持有block對象,就會造成循環引用循環引用(retain cycle),導致內存泄露,比如以下代碼

 self.block = ^{
 [self description];
 };

一般我們是這么解決的,使用一個__weal修飾的weakSelf變量指向self對象,在block中使用weakSelf:

 __weak typeof(self) weakSelf = self;
 self.block = ^{
 [weakSelf description];
 };

但是醬紫寫,還是可能出問題,因為weakSelf是弱引用,而self一旦釋放了,weakSelf可能為nil,還是舉個栗子吧:
1.先定義一個TestObj對象,他的屬性有一個block對象

@interface TestObj : NSObject
@property (nonatomic, copy)void(^block)();
@end

@implementation TestObj
- (void)dealloc {
 NSLog(@"%s",__func__);
}
- (instancetype)init {
 self = [super init];
 if (self) {
 __weak typeof(self) weakSelf = self;
 self.block = ^{
 dispatch_async(dispatch_get_global_queue(0, 0), ^{
 [NSThread sleepForTimeInterval:1];
 NSLog(@"%@",weakSelf);
 });
 };
 }
 return self;
}

@end

2.再另一個類實例中定義一個testFunc方法

- (void)testFunc{
 TestObj *obj = [TestObj new];
 obj.block();
}

執行testFunc方法,結果是打印的是(null),因為block里打印的方法是異步執行的,在 NSLog(@"%@",weakSelf);這句代碼執行之前testFunc函數就結束,所以obj對象已經被release了。
怎么解決呢?所以再對weakSelf做一次 __strong就可以了:

__weak typeof(self) weakSelf = self;
 self.block = ^{
 __strong typeof(weakSelf) strongSelf = weakSelf;
 dispatch_async(dispatch_get_global_queue(0, 0), ^{
 [NSThread sleepForTimeInterval:1];
 NSLog(@"%@",strongSelf);
 });
 };
}

使用了__strongstrongSelf變量作用域結束之前,對weakSelf有一個引用,防止對象(self)提前被釋放。而作用域一過,strongSelf不存在了,對象(self)也會被釋放。

1.問題

前面的寫法雖然嚴謹了,也解決了問題了,但是作為喜歡偷懶的程序猿,會不會覺得很啰嗦?每次都要寫那兩條長長的__weak__strong,而且在block里用到的self的全部要改成strongSelf,假設把一段很多self的代碼拷貝到block里,一個個改成strongSelf是不是很蛋疼?

2.RAC是怎么解決的

@weakify(self);
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
 @strongify(self);
 [self popViewControllerAnimated:YES];
}];

只要在block外用了@weakify(self);然后再block里寫@strongify(self);就可以了,@strongify(self);語句后的的self可以原封不動,好像很神奇,下面一起看看@weakify、@strongify 這兩個神奇的宏最終替換了什么東西。
導入RAC的頭文件,把上面的測試代碼替換成RAC中用的@weakify(self);和@strongify(self), 分屏顯示Xcode,讓右側的顯示內容改為 preprocess“,就可以看到宏最終替換的結果。

Paste_Image.png
  1. @autoreleasepool {} 是什么鬼?
    注意到@weakify(self)前面的@顏色并不是橙色沒有?@并不屬于宏的一部分,當然你不能平白無故寫個@對吧,所以RAC的weakify宏定義機智地給你補了一句autoreleasepool {} 這樣一前一后就變成了啥事都沒干的@autoreleasepool {}
  2. __attribute__((objc_ownership(weak)))是什么鬼?
    這個就是__weak在編譯前被編譯器替換的結果,而weakify這個宏最終替換的代碼包含有__weak(后面說到),所以編譯器再替換就成了__attribute__((objc_ownership(weak)))

2.weakify、strongify的定義

預備知識

  1. ...__VA_ARGS__
    看下NSLogprintf,他們的傳入參數有多個,用...表示不確定參數個數,
    看看NSLog的定義:
    NSLog(NSString *format, ...)
    在宏里也可以用...來表示多個參數,而__VA_ARGS__就對應多個參數的部分。
    舉個例子,你覺得NSLog太難看,想造一個自己的log打印函數,比如Zlog你就可以這么寫:
    #define Zlog(...) NSLog(__VA_ARGS__)
  1. 宏連接符##:
    ##這個符號 會將出現在 ## 左右兩邊連接的東西起來,舉個例子:
    宏定義為#define XLink(n) x ## n,這宏的意思是把x和傳入的n連接起來書寫:
#define XLink(n) x ## n
int x1 = 1;
int x2 = 2;
int x3 = 3;
//打印x1 x2 x3
NSLog(@"%d",XLink(1)); //NSlog(@"%zd",x1);
NSLog(@"%d",XLink(2)); //NSlog(@"%zd",x2);
NSLog(@"%d",XLink(3)); //NSlog(@"%zd",x3);

一層層展開weakify

假設我們寫了@weakify(self) 發生了什么
第一層:

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

rac_keywordify 實際上就是autoreleasepool {}的宏替換
VA_ARGS就是對應我們傳入的參數
這里我們就可以變成這樣:

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

第二層:

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

這一層開始就比較神奇了,
替換第一層的結果就變成這樣:

 autoreleasepool {}
 metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)

我們先看metamacro_argcount(self)這部分,metamacro_argcount(...)這個宏很強大,可以替換可變參數(...)的個數:
舉個例子
metamacro_argcount(@"obj") > 會被替換成1
metamacro_argcount(@"obj",@“obj”) > 會被替換成2
所以metamacro_argcount(self) > 會被替換成1(只有一個參數)
再看看metamacro_concat的定義:

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

居然還包了一次,那好,再點進去看看metamacro_concat_的定義

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

嗯,搞了半天就是之前說到的宏連接符 ##
所以metamacro_concat(A, B) 就是把A、B連接起來變成AB
根據上面分析的metamacro_argcount(self) > 1 ,再用metamacro_concat連接:

 autoreleasepool {}
 metamacro_foreach_cxt ## 1 (rac_weakify_, , __weak, self)

也就是:

 autoreleasepool {}
 metamacro_foreach_cxt1(rac_weakify_, , __weak, self)

第三層:
metamacro_foreach_cxt1 沒錯,不要懷疑,他還定義了metamacro_foreach_cxt1這個后面數組為1的宏,搜索一下:

Paste_Image.png

嗯,你沒猜錯,有metamacro_foreach_cxt1就有metamacro_foreach_cxt2、3、4、5、6、7、8...,
這些是什么鬼,我們先不管,先看我們的metamacro_foreach_cxt1

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

替換第二層分析的結果:

autoreleasepool {}
rac_weakify_(0,__weak,self) 

額,好像明朗起來了,毫不猶豫看看rac_weakify這個宏是怎么定義的:

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

再替換一次:
__weak __typeof__(self) self ## _weak_ = self
最終真相大白變成:

autoreleasepool {}
 __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;

同理 strongify(self) 這個宏最終展開是這樣的,注意這里他重新定義了self,在block語句塊里面是允許這么干的,之后用self就是文章一開頭的使用strongSelf一樣。

autoreleasepool {}
__typeof__(self) self = self_weak_;

褲子都脫了 你就給我看這個?有人要問了 為什么不直接用
rac_weakify_就好,搞那么復雜饒了一大圈,這不是裝逼么 - -
其實饒了一大圈的函數就是 @weakify(...);可以支持最多20個參數
比如: @weakify(ob1,obj2...,obj20);
最終會替換成:

@autoreleasepool {}
__weak type(obj1) obj1_weak_ = obj1;
__weak type(obj2) obj2_weak_ = obj2;
...
__weak type(obj20) obj20_weak_ = obj20;

下面,我們來講一講RAC是怎么裝逼的。

3.RAC裝逼宏

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_at(self)
就變成:
metamacro_at(20, self, 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)(self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

之前已經知道metamacro_concat是連接宏,所以變成

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

搜索metamacro_at20

Paste_Image.png

呵呵 又是一堆亂七八糟的,沒事看懂一個就全懂了。

#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__)

嗯,這個宏的意思就是 去掉傳入參數中前20個參數,把剩下的參數傳入metamacro_head宏,上面的metamacro_at20 前20個參數就是:
self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2
所以剩下的參數是 1
假設一開始不是 metamacro_at(self)而是兩個或多個參數會發生什么?
比如 metamacro_at(self,self)?
根據上面的規則替換,就會變成

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

除去前面20個參數,剩下 只有 2,1 所以把 2,1 這兩個參數傳入metamacro_head(2,1)
看看的metamacro_head的定義

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

又是一層,繼續點進去metamacro_head_

#define metamacro_head_(FIRST, ...) FIRST

其實就是截取第一個參數,所以
metamacro_head(2,1)就是 2。
而前面的metamacro_head(1)就是 1。
到這里 相信你已經弄清楚 metamacro_at 是怎么替換成參數個數的了

其他metamacro_at 也是一個道理

metamacro_foreach_cxt 的定義

回過頭看metamacro_foreach_cxt

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

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

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
 metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
 SEP \
 MACRO(2, CONTEXT, _2)
省略N多行

回到最開始,舉個例子
metamacro_argcount(obj1,obj2,obj3)
通過上面metamacro_argcount宏,確定出參數為3后,對號入座傳入
metamacro_foreach_cxt3

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
 metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
 SEP \
 MACRO(2, CONTEXT, _2)

所以也就變成了

metamacro_foreach_cxt3(rac_weakify_, , __weak , obj1 ,obj2 ,obj3)

發現是個遞歸,也就是

metamacro_foreach_cxt2(rac_weakify_, , CONTEXT, obj1, obj2) 
rac_weakify_(2, __weak, obj3)

而metamacro_foreach_cxt2 又是一層遞歸,最后
obj1、obj2、obj3都被替換成了:

__weak type(obj1) obj1_weak_ = obj1;
__weak type(obj2) obj2_weak_ = obj2;
__weak type(obj3) obj3_weak_ = obj3;

RAC的宏裝逼過程總結

其實總結起來很簡單,就2點:

  1. 通過metamacro_argcount確定可變參數個數x
  2. 根據1得到的x調用metamacro_foreach_cxtx,層層遞歸,對每個參數進行宏替換
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容