很長的前言
在block語句塊中疟赊,如果需引用self,而self對象中又持有block對象峡碉,就會造成循環(huán)引用循環(huán)引用(retain cycle)
近哟,導(dǎo)致內(nèi)存泄露,比如以下代碼
self.block = ^{
[self description];
};
一般我們是這么解決的异赫,使用一個__weak
修飾的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();
}
執(zhí)行testFunc
方法,結(jié)果是打印的是(null)颂碧,因為block里打印的方法是異步執(zhí)行的荠列,在 NSLog(@"%@",weakSelf);
這句代碼執(zhí)行之前testFunc
函數(shù)就結(jié)束,所以obj
對象已經(jīng)被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);
});
};
}
使用了__strong
在dispatch_async 里邊 block 代碼執(zhí)行完畢之前,對self
有一個引用诉瓦,防止對象(self)提前被釋放川队。而作用域一過,strongSelf
不存在了睬澡,對象(self)也會被釋放固额,具體可以看看我之前寫的這篇文章。
1.問題
前面的寫法雖然嚴謹了煞聪,也解決了問題了斗躏,但是作為喜歡偷懶的程序猿,會不會覺得很啰嗦昔脯?每次都要寫那兩條長長的__weak
和__strong
啄糙,而且在block里用到的self的全部要改成strongSelf,假設(shè)把一段很多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 這兩個神奇的宏最終替換了什么東西窿给。導(dǎo)入RAC的頭文件贵白,把上面的測試代碼替換成RAC中用的@weakify(self);和@strongify(self), 分屏顯示Xcode,讓右側(cè)的顯示內(nèi)容改為 preprocess“,就可以看到宏最終替換的結(jié)果崩泡。
- @autoreleasepool {} 是什么鬼禁荒?
注意到@weakify(self)前面的@顏色并不是橙色沒有?@
并不屬于宏的一部分角撞,當(dāng)然你不能平白無故寫個@對吧呛伴,所以RAC的weakify宏定義機智地給你補了一句autoreleasepool {}
這樣一前一后就變成了啥事都沒干的@autoreleasepool {}
-
attribute((objc_ownership(weak)))是什么鬼?
這個就是__weak在編譯前被編譯器替換的結(jié)果谒所,而weakify這個宏最終替換的代碼包含有__weak(后面說到)热康,所以編譯器再替換就成了attribute((objc_ownership(weak)))
2.weakify、strongify的定義
預(yù)備知識
-
...
和__VA_ARGS__
看下NSLog
和printf
劣领,他們的傳入?yún)?shù)有多個姐军,用...表示不確定參數(shù)個數(shù),看看NSLog的定義:NSLog(NSString *format, ...)
在宏里也可以用...
來表示多個參數(shù)尖淘,而__VA_ARGS__
就對應(yīng)多個參數(shù)的部分奕锌。舉個例子,你覺得NSLog太難看村生,想造一個自己的log打印函數(shù)惊暴,比如Zlog你就可以這么寫:#define Zlog(...) NSLog(__VA_ARGS__)
- 宏連接符
##
:
##
這個符號 會將出現(xiàn)在 ##
左右兩邊連接的東西起來,舉個例子:宏定義為#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
假設(shè)我們寫了@weakify(self) 發(fā)生了什么第一層:
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
rac_keywordify
實際上就是autoreleasepool {}
的宏替換而VA_ARGS就是對應(yīng)我們傳入的參數(shù)這里我們就可以變成這樣:
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__)
這一層開始就比較神奇了趁桃,替換第一層的結(jié)果就變成這樣:
autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)
我們先看metamacro_argcount(self)
這部分辽话,metamacro_argcount(...)
這個宏很強大,可以替換可變參數(shù)(...)的個數(shù):舉個例子metamacro_argcount(@"obj") > 會被替換成1
metamacro_argcount(@"obj"镇辉,@“obj”) > 會被替換成2
所以metamacro_argcount(self) > 會被替換成1
(只有一個參數(shù))再看看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根據(jù)上面分析的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這個后面數(shù)組為1的宏础废,搜索一下:
嗯,你沒猜錯罕模,有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)
替換第二層分析的結(jié)果:
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 {}__weak __typeof__(self) self_weak_ = self
同理 strongify(self) 這個宏最終展開是這樣的,注意這里他重新定義了self马昙,在block語句塊里面是允許這么干的桃犬,之后用self就是文章一開頭的使用strongSelf一樣。
autoreleasepool {}
__attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
褲子都脫了 你就給我看這個?有人要問了 為什么不直接用rac_weakify_就好行楞,搞那么復(fù)雜饒了一大圈攒暇,這不是裝逼么 - -其實饒了一大圈的函數(shù)就是 @weakify(...);可以支持最多20個參數(shù)比如: @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這個宏可以把可變參數(shù)...替換成參數(shù)的個數(shù)扯饶,看看他的定義:
#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)
一臉懵逼?沒關(guān)系池颈,我們一層層來看:假設(shè):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)
之前已經(jīng)知道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
呵呵 又是一堆亂七八糟的,沒事看懂一個就全懂了躯砰。
#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__)
嗯每币,這個宏的意思就是 去掉傳入?yún)?shù)中前20個
參數(shù),把剩下的
參數(shù)傳入metamacro_head宏琢歇,上面的metamacro_at20 前20個參數(shù)就是:self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2
所以剩下的參數(shù)是 1
假設(shè)一開始不是 metamacro_at(self)而是兩個或多個參數(shù)會發(fā)生什么兰怠?比如 metamacro_at(self,self)?根據(jù)上面的規(guī)則替換李茫,就會變成
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個參數(shù)揭保,剩下 只有 2,1 所以把 2,1 這兩個參數(shù)傳入metamacro_head(2,1)
看看的metamacro_head
的定義
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
又是一層魄宏,繼續(xù)點進去metamacro_head_
#define metamacro_head_(FIRST, ...) FIRST
其實就是截取第一個參數(shù)秸侣,所以metamacro_head(2,1)就是 2。而前面的metamacro_head(1)就是 1宠互。到這里 相信你已經(jīng)弄清楚 metamacro_at 是怎么替換成參數(shù)個數(shù)的了
其他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宏,確定出參數(shù)為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)
發(fā)現(xiàn)是個遞歸搏色,也就是
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的宏裝逼過程總結(jié)
其實總結(jié)起來很簡單垂涯,就2點:
- 通過metamacro_argcount確定可變參數(shù)個數(shù)
x
- 根據(jù)1得到的
x
調(diào)用metamacro_foreach_cxtx
,層層遞歸,對每個參數(shù)進行宏替換
本文引用自這里,如有侵權(quán)航邢,請聯(lián)系本人刪除集币!