Reactive Cocoa Tutorial 系列,,轉(zhuǎn)載請(qǐng)注明該文源地址 – by sunnyxx
先說說RAC中必須要知道的宏:RAC(TARGET, [KEYPATH, [NIL_VALUE]]) |
使用:
RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal; RAC(self.outputLabel, text, @"收到nil時(shí)就顯示我") = self.inputTextField.rac_textSignal;
|
這個(gè)宏是最常用的,,RAC() 總是出現(xiàn)在等號(hào)左邊,等號(hào)右邊是一個(gè)RACSignal ,,表示的意義是將一個(gè)對(duì)象的一個(gè)屬性 和一個(gè)signal 綁定,,signal每產(chǎn)生一個(gè)value(id類型),,都會(huì)自動(dòng)執(zhí)行:
[TARGET setValue:value ?: NIL_VALUE forKeyPath:KEYPATH];
|
數(shù)字值會(huì)升級(jí)為NSNumber * ,,當(dāng)setValue:forKeyPath時(shí)會(huì)自動(dòng)降級(jí)成基本類型(int, float ,BOOL等),所以RAC綁定一個(gè)基本類型的值是沒有問題的
· RACObserve(TARGET, KEYPATH)
|
作用是觀察TARGET的KEYPATH屬性,,相當(dāng)于KVO ,,產(chǎn)生一個(gè)RACSignal
最常用的使用,和RAC宏綁定屬性:
RAC(self.outputLabel, text) = RACObserve(self.model, name);
|
上面的代碼將label的輸出和model的name屬性綁定,,實(shí)現(xiàn)聯(lián)動(dòng),,name但凡有變化都會(huì)使得label輸出
@weakify(Obj); @strongify(Obj);
|
這對(duì)宏在 RACEXTScope.h 中定義,RACFramework好像沒有默認(rèn)引入,,需要單獨(dú)import
他們的作用主要是在block內(nèi)部管理對(duì)self的引用:
@weakify(self); // 定義了一個(gè)__weak的self_weak_變量 [RACObserve(self, name) subscribeNext:^(NSString *name) { @strongify(self); // 局域定義了一個(gè)__strong的self指針指向self_weak self.outputLabel.text = name; }];
|
這個(gè)宏為什么這么吊,,前面加@,其實(shí)就是一個(gè)啥都沒干的@autoreleasepool {}前面的那個(gè)@,,為了顯眼罷了,。
這兩個(gè)宏一定成對(duì)出現(xiàn),先weak再strong
除了RAC中常用宏的使用,有一些宏的實(shí)現(xiàn)方法也很值得觀摩,。 舉個(gè)高級(jí)點(diǎn)的栗子:
要干的一件事,,計(jì)算一個(gè)可變參數(shù)列表的長度。
第一反應(yīng)就是用參數(shù)列表的api,,va_start va_arg va_end 遍歷一遍計(jì)算個(gè)和,,但仔細(xì)想想,對(duì)于可變參數(shù)這個(gè)事,,在編譯前其實(shí)就已經(jīng)確定了,,代碼里括號(hào)里有多少個(gè)參數(shù)一目了然。
RAC中Racmetamarcos.h 中就有一系列宏來完成這件事,,硬是在預(yù)處理之后就拿到了可變參數(shù)個(gè)數(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)
|
這個(gè)宏由幾個(gè)工具宏一層層展開,,現(xiàn)在模擬一下展開過程:
假如我們要計(jì)算的如下:
int count = metamacro_argcount(a, b, c);
|
于是乎第一層展開后:
int count = metamacro_at(20, a, b, c, 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做的事(簡寫一層) #define metamacro_concat_(A, B) A ## B
|
于是乎第二層展開后:
int count = metamacro_at20(a, b, c, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
|
再看metamacro_at20這個(gè)宏干的事兒:
#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__)
|
于是乎第三層展開后,,相當(dāng)于截?cái)嗔饲?0個(gè)參數(shù),留下剩下幾個(gè):
int count = metamacro_head(3, 2, 1);
|
這個(gè)metamacro_head:
#define metamacro_head(...) metamacro_head_(__VA_ARGS__, 0) #define metamacro_head_(FIRST, ...) FIRST
|
后面加個(gè)0,,然后取參數(shù)列表第一個(gè),,于是乎:
大功告成。
反正我看完之后感覺挺震驚,,宏還能這么用,,這樣帶來的好處不止是將計(jì)算在預(yù)處理時(shí)搞定,不拖延到運(yùn)行時(shí)惡心cpu,;但更重要的是編譯檢查,。比如某些可變參數(shù)的實(shí)現(xiàn)要求可以填2個(gè)參數(shù),可以填3個(gè)參數(shù),,其他的都不行,,這樣,,也只有這樣的宏的實(shí)現(xiàn),,才能在編譯前就確定了錯(cuò)誤。
除了上面,,還有一個(gè)神奇的宏的使用: 當(dāng)使用諸如RAC(self, outputLabel) 或RACObserve(self, name) 時(shí),發(fā)現(xiàn)寫完逗號(hào)之后,,輸入第二個(gè)property的時(shí)候會(huì)出現(xiàn)完全正確的代碼提示,!這相當(dāng)神奇。
探究一下,,關(guān)鍵的關(guān)鍵是如下一個(gè)宏:
#define keypath(...) \ metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
|
這個(gè)metamacro_argcount 上面說過,,是計(jì)算可變參數(shù)個(gè)數(shù),所以metamacro_if_eq 的作用就是判斷參數(shù)個(gè)數(shù),,如果個(gè)數(shù)是1就執(zhí)行后面的keypath1,,若不是1就執(zhí)行keypath2。
所以重點(diǎn)說一下keypath2:
#define keypath2(OBJ, PATH) \ (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
|
乍一看真挺懵,,先化簡,,由于Objc里面keypath是諸如”outputLabel.text”的字符串,所以這個(gè)宏的返回值應(yīng)該是個(gè)字符串,,可以簡化成:
#define keypath2(OBJ, PATH) (???????, # PATH)
|
先不管”??????”是啥,,這里不得不說C語言中一個(gè)不大常見的語法(第一個(gè)忽略):
int a = 0, b = 0; a = 1, b = 2; int c = (a, b);
|
這些都是逗號(hào)表達(dá)式的合理用法,第三個(gè)最不常用了,,c將被b賦值,,而a是一個(gè)未使用的值,編譯器會(huì)給出warning,。
去除warning的方法很簡單,,強(qiáng)轉(zhuǎn)成void就行了:
再看上面簡化的keypath2宏,返回的就是PATH的字符串字面值了(單#號(hào)會(huì)將傳入值轉(zhuǎn)成字面字符串)
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
|
對(duì)傳入的第一個(gè)參數(shù)OBJ和第二個(gè)正要輸入的PATH做了點(diǎn) 操作,這也正是為什么輸入第二個(gè)參數(shù)時(shí)編輯器會(huì)給出正確的代碼提示,。強(qiáng)轉(zhuǎn)void就像上面說的去除了warning,。
但至于為什么加入與NO 做&& ,我不太能理解,,我測試時(shí)其實(shí)沒有時(shí)已經(jīng)完成了功能,,可能是作者為了屏蔽某些隱藏的問題吧。
這個(gè)宏的巧妙的地方就在于使得編譯器以為我們要輸入“點(diǎn)”出來的屬性,,保證了輸入值的合法性(輸了不存在的property直接報(bào)錯(cuò)的),,同時(shí)利用了逗號(hào)表達(dá)式取逗號(hào)最后值的語法返回了正確的keypath。
總之RAC對(duì)宏的使用達(dá)到了很高的水平,,還有諸如RACTuplePack ,,RACTupleUnpack 的宏就不細(xì)說了,值得研究,。
PS:上面介紹的metamacro和@strongify等宏確切來說來自RAC依賴的extobjc,,作者是Justin Spahr-Summers,正是RAC作者之一,。
|