天天看點

ReactiveCocoa源碼解析

(一)神奇的Macros  先說說RAC中必須要知道的宏

  1. RAC(TARGET, [KEYPATH, [NIL_VALUE]]) 

  使用:

  1. RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal; 
  2. RAC(self.outputLabel, text, @"收到nil時就顯示我") = self.inputTextField.rac_textSignal; 

  這個宏是最常用的,RAC()總是出現在等号左邊,等号右邊是一個RACSignal,表示的意義是将一個對象的一個屬性和一個signal綁定,signal每産生一個value(id類型),都會自動執行:

  1. [TARGET setValue:value ?: NIL_VALUE forKeyPath:KEYPATH]; 

  數字值會更新為NSNumber *,當setValue:forKeyPath時會自動降級成基本類型(int, float ,BOOL等),是以RAC綁定一個基本類型的值是沒有問題的

  1. RACObserve(TARGET, KEYPATH) 

  作用是觀察TARGET的KEYPATH屬性,相當于KVO,産生一個RACSignal   最常用的使用,和RAC宏綁定屬性:

  1. RAC(self.outputLabel, text) = RACObserve(self.model, name); 

  上面的代碼将label的輸出和model的name屬性綁定,實作關聯,name但凡有變化都會使得label輸出

  1. @weakify(Obj); 
  2. @strongify(Obj); 

  這對宏在 RACEXTScope.h 中定義,RACFramework好像沒有預設引入,需要單獨import   他們的作用主要是在block内部管理對self的引用:

  1. @weakify(self); // 定義了一個__weak的self_weak_變量 
  2. [RACObserve(self, name) subscribeNext:^(NSString *name) { 
  3.     @strongify(self); // 局域定義了一個__strong的self指針指向self_weak 
  4.     self.outputLabel.text = name; 
  5. }]; 

  這個宏為什麼這麼吊,前面加@,其實就是一個啥都沒幹的@autoreleasepool {}前面的那個@,為了顯眼罷了。   這兩個宏一定成對出現,先weak再strong   除了RAC中常用宏的使用,有一些宏的實作方法也很值得觀摩。   舉個進階點的例子:  要幹的一件事,計算一個可變參數清單的長度。   第一反應就是用參數清單的api,va_start va_arg va_end周遊一遍計算個和,但仔細想想,對于可變參數這個事,在編譯前其實就已經确定了,代碼裡括号裡有多少個參數一目了然。   RAC中Racmetamarcos.h中就有一系列宏來完成這件事,硬是在預處理之後就拿到了可變參數個數:

  1. #define metamacro_argcount(...) \ 
  2.     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) 

  這個宏由幾個工具宏一層層展開,現在模拟一下展開過程: 1. 假如我們要計算的如下:

  1. int count = metamacro_argcount(a, b, c); 

  2. 于是乎第一層展開後:

  1. 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) 

  3. 再看metamacro_at的定義:

  1. #define metamacro_at(N, ...) metamacro_concat(metamacro_at, N)(__VA_ARGS__) 
  2. // 下面是metamacro_concat做的事(簡寫一層) 
  3. #define metamacro_concat_(A, B) A ## B 

  4. 于是乎第二層展開後:

  1. 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); 

 5. 再看metamacro_at20這個宏幹的事兒:

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

 6. 于是乎第三層展開後,相當于截斷了前20個參數,留下剩下幾個:

  1. int count = metamacro_head(3, 2, 1); 

 7. 這個metamacro_head:

  1. #define metamacro_head(...) metamacro_head_(__VA_ARGS__, 0) 
  2. #define metamacro_head_(FIRST, ...) FIRST 

  8. 後面加個0,然後取參數清單第一個,于是乎:

  1. int count = 3; 

大功告成。   反正我看完之後感覺挺震驚,宏還能這麼用,這樣帶來的好處不止是将計算在預處理時搞定,不拖延到運作時惡心cpu;但更重要的是編譯檢查。比如某些可變參數的實作要求可以填2個參數,可以填3個參數,其他的都不行,這樣,也隻有這樣的宏的實作,才能在編譯前就确定了錯誤。   除了上面,還有一個神奇的宏的使用  當使用諸如RAC(self, outputLabel)或RACObserve(self, name)時,發現寫完逗号之後, 輸入第二個property的時候會出現完全正确的代碼提示!這相當神奇。 

ReactiveCocoa源碼解析

  探究一下,關鍵的關鍵是如下一個宏:

  1. #define keypath(...) \ 
  2.     metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__)) 

  這個metamacro_argcount上面說過,是計算 可變參數個數,是以metamacro_if_eq的作用就是判斷參數個數,如果個數是1就執行後面的keypath1,若不是1就執行keypath2。   是以 重點說一下keypath2:

  1. #define keypath2(OBJ, PATH) \ 
  2.     (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) 

先化簡,由于Objc裡面keypath是諸如”outputLabel.text”的字元串,是以這個宏的傳回值應該是個字元串,可以簡化成:

  1. #define keypath2(OBJ, PATH) (???????, # PATH) 

  先不管”??????”是啥,這裡不得不說C語言中一個不大常見的文法(第一個忽略):

  1. int a = 0, b = 0; 
  2. a = 1, b = 2; 
  3. int c = (a, b); 

  這些都是 逗号表達式的合理用法,第三個最不常用了,c将被b指派,而a是一個未使用的值,編譯器會給出warning。   去除warning的方法很簡單,強轉成void就行了:

  1. int c = ((void)a, b); 

  再看上面簡化的keypath2宏,傳回的就是PATH的字元串字面值了(單#号會将傳入值轉成字面字元串)

  1. (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) 

  對傳入的第一個參數OBJ和第二個正要輸入的PATH做了點操作,這也正是為什麼輸入第二個參數時編輯器會給出正确的代碼提示。強轉void就像上面說的去除了warning。   但至于為什麼加入與NO做&&,我不太能了解,我測試時其實沒有時已經完成了功能,可能是作者為了屏蔽某些隐藏的問題吧。   這個宏的巧妙的地方就在于使得編譯器以為我們要輸入“點”出來的屬性,保證了輸入值的合法性(輸了不存在的property直接報錯的),同時利用了逗号表達式取逗号最後值的文法傳回了正确的keypath。   總之 RAC對宏的使用達到了很高的水準,還有諸如RACTuplePack,RACTupleUnpack的宏就不細說了,值得研究。   PS:上面介紹的metamacro和@strongify等宏确切來說來自RAC依賴的extobjc,作者是Justin Spahr-Summers,正是RAC作者之一。