ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

一縷殤流化隱半邊冰霜發表於2019-03-04

前言

在ReactiveCocoa 中,開源庫作者為我們提供了很多種魔法,“黑”魔法,“紅”魔法……今天就讓先來看看“紅”魔法。

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

在ReactiveCocoa 中,封裝了很多非常實用的“巨集”,使用這些“巨集”為我們開發帶來了很多的便利。

今天就來盤點一下RAC中的巨集是如何實現的。

目錄

  • 1.關於巨集
  • 2.ReactiveCocoa 中的元巨集
  • 3.ReactiveCocoa 中常用的巨集

一. 關於巨集

巨集(Macro),是一種批量處理的稱謂。

在程式設計領域裡的巨集是一種抽象(Abstraction),它根據一系列預定義的規則替換一定的文字模式。直譯器編譯器在遇到巨集時會自動進行這一模式替換。絕大多數情況下,“巨集”這個詞的使用暗示著將小命令或動作轉化為一系列指令。

巨集的用途在於自動化頻繁使用的序列或者是獲得一種更強大的抽象能力。
計算機語言如C語言組合語言有簡單的巨集系統,由編譯器彙編器的前處理器實現。C語言的巨集前處理器的工作只是簡單的文字搜尋和替換,使用附加的文字處理語言如M4,C程式設計師可以獲得更精巧的巨集。

Lisp類語言如Common LispScheme有更精巧的巨集系統:巨集的行為如同是函式對自身程式文字的變形,並且可以應用全部語言來表達這種變形。一個C巨集可以定義一段語法的替換,然而一個Lisp的巨集卻可以控制一節程式碼的計算。

對於編譯語言來說,所有的巨集都是在預編譯的時候被展開的,所以在lex進行詞法掃描生成Token,詞法分析過程之前,所有的巨集都已經被展開完成了。

對於Xcode,預處理或者預編譯階段是可以直接檢視的。

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

隨便寫一個巨集,然後開啟Xcode右上方的Assistant,選擇“Preprocess”就可以看到該檔案預處理之後的樣子了。可以看到左邊的@weakify(self) 被轉換成了右邊的兩行程式碼了。

關於這個Xcode的這個功能還有2點補充說明:

1.不同階段的Preprocessed可能不同,要根據你的目標去選擇預處理的條件。

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

比如這裡就有5種預編譯的種類可以選擇。

2.巨集經過預編譯之後出來的程式碼,是可以用來檢測巨集寫的是否正確的,但是無法看到巨集被展開的具體過程。這意味著我們可以通過Xcode這個功能來檢視巨集的作用,但是無法知道巨集的具體實現。具體實現還是需要通過檢視原始碼來分析。

ReactiveCocoa中的巨集,如果不檢視原始碼分析,會覺得那些巨集都像魔法一樣奇妙無比,接下來就來解開“巨集”魔法的神祕面紗。

二. ReactiveCocoa 中的元巨集

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

在ReactiveCocoa的巨集中,作者定義了這麼一些基礎的巨集,作為“元巨集”,它們是構成之後複雜巨集的基礎。在分析常用巨集之前,必須要先分析清楚這些元巨集的具體實現。

1. metamacro_stringify(VALUE)


#define metamacro_stringify(VALUE) \
        metamacro_stringify_(VALUE)

#define metamacro_stringify_(VALUE) # VALUE複製程式碼

metamacro_stringify( )這個巨集用到了#的用法。#在巨集中代表把巨集的引數變為一個字串。這個巨集的目的和它的名字一樣明顯,把入參VALUE轉換成一個字串返回。

這裡可能就有人有疑問,為啥要包裝一層,不能直接寫成下面這樣:


#define metamacro_stringify(VALUE)  # VALUE複製程式碼

語意確實也沒有變,但是有種特殊情況下就會出現問題。

舉個例子:


#define NUMBER   10
#define ADD(a,b) (a+b)
NSLog(@"%d+%d=%d",NUMBER, NUMBER, ADD(NUMBER,NUMBER));複製程式碼

輸出如下:


10+10=20複製程式碼

這樣子確實是沒有問題,但是稍作修改就會有問題。


#define STRINGIFY(S) #S
#define CALCULATE(A,B)  (A##10##B)

NSLog(@"int max: %s",STRINGIFY(INT_MAX));
NSLog(@"%d", CALCULATE(NUMBER,NUMBER));複製程式碼

如果是這種情況下,第二個NSLog列印是會編譯錯誤的。上面兩句經過預編譯之後,巨集會被展開成下面這個樣子:


NSLog(@"int max: %s","INT_MAX");
NSLog(@"%d", (NUMBER10NUMBER));複製程式碼

可以發現,巨集並沒有再次被展開。解決辦法也很簡單,就是把巨集包裝一層,寫一個轉接巨集出來。


#define CALCULATE(A,B)   _CALCULATE(A,B)   // 轉換巨集
#define _CALCULATE(A,B)  A##10##B複製程式碼

再次測試一下,這裡我們使用官方的metamacro_stringify


NSLog(@"int max: %s",metamacro_stringify(INT_MAX));
NSLog(@"%d", CALCULATE(NUMBER,NUMBER));複製程式碼

這樣最終列印出來的結果和我們想要的一致,沒有問題。


2147483647
101010複製程式碼

CALCULATE(NUMBER,NUMBER) 第一層轉換成 _CALCULATE(10,10),接著第二次轉換成10##10##10,也就是101010。

當然這裡是2層轉換,如果有多層轉換就需要更多個轉換巨集了。


NSLog(@"%d", CALCULATE(STRINGIFY(NUMBER),STRINGIFY(NUMBER)));複製程式碼

上面這個例子就是3層了,按照之前我們的寫法還是編譯報錯。如果是超過2,3層的多層的情況,就該考慮考慮巨集設計的語意的問題,儘量不讓使用者產生錯誤的用法。

2. metamacro_concat(A, B)


#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B複製程式碼

這個巨集就是用來合併入參A,B到一起。在RAC裡面主要用這個方法來合成另外一個巨集的名字。

3. metamacro_argcount(...) 和 metamacro_at(N, ...)

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(N, ...)巨集。


#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)複製程式碼

把這個巨集展開,於是得到:


#define metamacro_at(N, ...) \
        metamacro_atN(__VA_ARGS__)複製程式碼

於是通過metamacro_concat合成命令,就得到了一連串的metamacro_atN巨集命令:


#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
#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__)複製程式碼

可見N的取值只能從0到20。


#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST複製程式碼

metamacro_head展開之後變成:


#define metamacro_head(FIRST,..., 0)  FIRST複製程式碼

metamacro_head的意圖就很明顯,是用來獲取後面可變入參的第一個引數。

回到metamacro_atN巨集上面來,那麼把它展開就是下面這樣:


#define metamacro_atN(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ... , _N, ...) metamacro_head(__VA_ARGS__)複製程式碼

當然,N的取值還是從0到20,那麼metamacro_atN巨集獲取到的值就是可變引數列表裡面的第N個引數值。引數從0開始。

再回到最初的metamacro_argcount(...)巨集,目前展開到這一步:



#define metamacro_argcount(...) \
        metamacro_at20(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)複製程式碼

由於__VA_ARGS__個數不能超過20個,所以必定是在0-19之間。


metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ..., 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) metamacro_head(__VA_ARGS__)複製程式碼

假設入參是有5個:


metamacro_argcount(@"1",@"2",@"3",@"4",@"5");複製程式碼

先把5個引數放入metamacro_at20的前五個位置。然後從第6個位置開始倒序插入20-1的數字。如下圖:

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

我們可以把倒序的數字想象成一把尺子,是用來衡量或者指示當前有多少個引數的。尺子的最左邊對齊上面20個空位的第一位,尺子後面多出來的部分,取出來,然後進行metamacro_head操作,取出第一位引數,這個數字就是整個引數的個數了。這把虛擬的“尺子”是會左右對齊的,具體的位置就要根據填入引數的個數來決定的。

這個巨集的原理也很簡單,20 - ( 20 - n )= n。metamacro_argcount(...) 巨集就是這樣在預編譯時期獲取到引數個數的。

作者也標明瞭,這個巨集的設計靈感來自於P99神庫,有興趣的同學可以去看看這個庫。

4. metamacro_foreach(MACRO, SEP, ...) 和 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)

先來分析分析metamacro_foreach(MACRO, SEP, ...) 巨集:


#define metamacro_foreach(MACRO, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)複製程式碼

看到定義就知道metamacro_foreach(MACRO, SEP, ...) 和 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) 是一樣的作用。前者只不過比後者少了一個foreach的迭代子。

1. metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集

再來看看metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集的定義。



#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)複製程式碼

那麼之前的metamacro_foreach(MACRO, SEP, ...)巨集就可以等價於


metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)複製程式碼

回到metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集的展開表示式上面來,假設__VA_ARGS__的引數個數為N。

metamacro_concat 巨集 和 metamacro_argcount 巨集上面介紹過了,那麼可以繼續把巨集展開成下面的樣子:


metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)複製程式碼

這裡又是利用metamacro_concat 巨集動態的合併成了另一個巨集的例子。


#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#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)

#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    SEP \
    MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    SEP \
    MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    SEP \
    MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    SEP \
    MACRO(6, CONTEXT, _6)

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

#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    SEP \
    MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    SEP \
    MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    SEP \
    MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    SEP \
    MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    SEP \
    MACRO(12, CONTEXT, _12)

#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    SEP \
    MACRO(13, CONTEXT, _13)

#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    SEP \
    MACRO(14, CONTEXT, _14)

#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    SEP \
    MACRO(15, CONTEXT, _15)

#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    SEP \
    MACRO(16, CONTEXT, _16)

#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    SEP \
    MACRO(17, CONTEXT, _17)

#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    SEP \
    MACRO(18, CONTEXT, _18)

#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
    metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    SEP \
    MACRO(19, CONTEXT, _19)複製程式碼

把上述的metamacro_foreach_cxtN的定義抽象一下:


#define metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, … ,_N - 1) \
    metamacro_foreach_cxtN - 1(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, … ,_N - 2) \
    SEP \
    MACRO(N - 1, CONTEXT, _N - 1)複製程式碼

當然,在RAC中N的取值範圍是[0,20]。我們還是假設N的定義域是全體非負整陣列成的集合N(數學中的非負整數集合的標誌) 。那麼我們把metamacro_foreach_cxtN完全展開到不能展開為止:


    MACRO(0, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1) \
    SEP \
    MACRO(2, CONTEXT, _2) \
    SEP \
    MACRO(3, CONTEXT, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, CONTEXT, _N - 4) \
     SEP \
    MACRO(N - 3, CONTEXT, _N - 3) \
     SEP \
    MACRO(N - 2, CONTEXT, _N - 2) \
     SEP \
    MACRO(N - 1, CONTEXT, _N - 1)複製程式碼

metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, ...),這個巨集的意圖也就很明顯了,從可變引數列表裡面讀取出個數,然後把每個引數都進行一次MACRO(N - 1, CONTEXT, _N - 1)操作,每個操作直接用SEP作為分隔符進行分隔。

metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)這個巨集的設計靈感也來自於P99庫。

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

用到這個巨集最著名的的巨集就是weakify(...)了,下面來簡要的看看是如何利用metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, ...)巧妙的實現weakify(...)的。


#define weakify(...) \
    rac_keywordify \
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)複製程式碼

使用weakify和平時我們自己寫的weakSelf最大的區別就是,weakify後面是可以跟多個引數的,最多多達20個。weakify可以一口氣把引數列表裡面所有的引數都進行weak操作。

weakify(...)的重點之一就在metamacro_foreach_cxt操作上。假設傳入2個引數,self和str,進行展開之後得到:



    MACRO(0, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)複製程式碼

MACRO = rac_weakify_,CONTEXT = __weak,SEP 為 空格 ,代入引數:


 rac_weakify_(0,__weak,self) \
 rac_weakify_(1,__weak,str)複製程式碼

注意,替換完成之後,兩個巨集是連在一起的,中間沒有分號!分隔符SEP目前是空格。最後一步就是替換掉rac_weakify_:


#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);複製程式碼

注意這裡的INDEX是廢引數,並沒有被用到。

展開上面的巨集:


__weak __typeof__(self) self_weak_ = (self);__weak __typeof__(str) str_weak_ = (str);複製程式碼

注意,rac_weakify_是自帶分號的,如果此處沒有分號,這裡會出現編譯錯誤。

最終@weakify(self,str) 就會在預編譯期間被替換成



@autoreleasepool {} __weak __typeof__(self) self_weak_ = (self);__weak __typeof__(str) str_weak_ = (str);複製程式碼

注意中間是沒有換行的,此處巨集展開之後就是一行。

metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)分析完畢之後再回過來看看metamacro_foreach(MACRO, SEP, ...)

2. metamacro_foreach(MACRO, SEP, ...)

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)複製程式碼

此時同樣可以假設引數個數為N,那麼上述巨集展開可以變成下面的樣子:



metamacro_foreach_cxtN(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)複製程式碼

這裡的MACRO = metamacro_foreach_iter,SEP = SEP , CONTEXT = MACRO。


    metamacro_foreach_iter(0, MACRO, _0) \
    SEP \
    metamacro_foreach_iter(1, MACRO, _1) \
    SEP \
    metamacro_foreach_iter(2, MACRO, _2) \
    SEP \
    metamacro_foreach_iter(3, MACRO, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    metamacro_foreach_iter(N - 4, MACRO, _N - 4) \
     SEP \
    metamacro_foreach_iter(N - 3, MACRO, _N - 3) \
     SEP \
    metamacro_foreach_iter(N - 2, MACRO, _N - 2) \
     SEP \
    metamacro_foreach_iter(N - 1, MACRO, _N - 1)複製程式碼

metamacro_foreach_iter 定義如下:



#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)複製程式碼

繼續展開得到下面的式子:


    MACRO(0, _0) \
    SEP \
    MACRO(1, _1) \
    SEP \
    MACRO(2, _2) \
    SEP \
    MACRO(3, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, _N - 4) \
     SEP \
    MACRO(N - 3, _N - 3) \
     SEP \
    MACRO(N - 2, _N - 2) \
     SEP \
    MACRO(N - 1, _N - 1)複製程式碼

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

從最終的展開式子上來看,metamacro_foreach(MACRO, SEP, ...) 就比 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) 少了一個CONTEXT。

metamacro_foreach(MACRO, SEP, ...)這個巨集的典型例子就是熟知的strongify(...)的實現。



#define strongify(...) \
    rac_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")複製程式碼

通過上面的分析,我們直接替換結果,MACRO = rac_strongify_ ,SEP = 空格。


    rac_strongify_(0, _0) \
    rac_strongify_(1, _1) \
    rac_strongify_(2, _2) \
    rac_strongify_(3, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

    rac_strongify_(N - 4, _N - 4) \
    rac_strongify_(N - 3, _N - 3) \
    rac_strongify_(N - 2, _N - 2) \
    rac_strongify_(N - 1, _N - 1)複製程式碼

接下來替換掉rac_strongify_


#define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);複製程式碼

同樣的,這裡的INDEX也是一個廢引數,也沒有用到。rac_strongify_同樣的自帶分號,如果此處沒有分號,SEP此時也是空格,編譯就直接報錯。

最終就轉換成如下的樣子:


__strong __typeof__(self) self = self_weak_;複製程式碼

5. metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...)

先來看看定義:


#define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)複製程式碼

假設可變引數個數為N,將上面式子展開:



#define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \
        metamacro_foreach_cxt_recursiveN(MACRO, SEP, CONTEXT, __VA_ARGS__)複製程式碼

於是就轉換成了metamacro_foreach_cxt_recursiveN 巨集:



#define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

#define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)

#define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \
    SEP \
    MACRO(2, CONTEXT, _2)

#define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    SEP \
    MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    SEP \
    MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    SEP \
    MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    SEP \
    MACRO(6, CONTEXT, _6)

#define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    SEP \
    MACRO(7, CONTEXT, _7)

#define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    SEP \
    MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    SEP \
    MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    SEP \
    MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    SEP \
    MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    SEP \
    MACRO(12, CONTEXT, _12)

#define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    SEP \
    MACRO(13, CONTEXT, _13)

#define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    SEP \
    MACRO(14, CONTEXT, _14)

#define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    SEP \
    MACRO(15, CONTEXT, _15)

#define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    SEP \
    MACRO(16, CONTEXT, _16)

#define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    SEP \
    MACRO(17, CONTEXT, _17)

#define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    SEP \
    MACRO(18, CONTEXT, _18)

#define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
    metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    SEP \
    MACRO(19, CONTEXT, _19)複製程式碼

提取一下metamacro_foreach_cxt_recursiveN的定義:


#define metamacro_foreach_cxt_recursiveN(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, … ,_( N - 1)) \
        metamacro_foreach_cxt_recursive(N - 1)(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _( N - 2)) \
        SEP \
        MACRO(N -1, CONTEXT, _N -1)複製程式碼

還是按照之前分析的,同理完全展開:


    MACRO(0, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1) \
    SEP \
    MACRO(2, CONTEXT, _2) \
    SEP \
    MACRO(3, CONTEXT, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, CONTEXT, _N - 4) \
     SEP \
    MACRO(N - 3, CONTEXT, _N - 3) \
     SEP \
    MACRO(N - 2, CONTEXT, _N - 2) \
     SEP \
    MACRO(N - 1, CONTEXT, _N - 1)複製程式碼

至此,展開式與metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集完全相同。

這個遞迴的巨集從來沒有在RAC的其他巨集中使用,作者在這裡標註說明了這個巨集的用處。

This can be used when the former would fail due to recursive macro expansion

由於巨集在遞迴展開中可能會導致遞迴前置條件失敗,在這種情況下,應該使用這個遞迴巨集。當然,它的效果和metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集是完全一樣的。

6. metamacro_foreach_concat(BASE, SEP, ...)

這個巨集定義是套用了metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集的實現,只是多傳入了一些引數。由此可見,metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集在RAC中的重要性。


#define metamacro_foreach_concat(BASE, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__)複製程式碼

由於在上面詳細分析過了metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)巨集的實現,那麼這裡就直接完全展開到最後一步。MACRO = metamacro_foreach_concat_iter,SEP = SEP,CONTEXT = BASE。


    metamacro_foreach_concat_iter(0, BASE, _0) \
    SEP \
    metamacro_foreach_concat_iter(1, BASE, _1) \
    SEP \
    metamacro_foreach_concat_iter(2, BASE, _2) \
    SEP \
    metamacro_foreach_concat_iter(3, BASE, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    metamacro_foreach_concat_iter(N - 4, BASE, _N - 4) \
     SEP \
    metamacro_foreach_concat_iter(N - 3, BASE, _N - 3) \
     SEP \
    metamacro_foreach_concat_iter(N - 2, BASE, _N - 2) \
     SEP \
    metamacro_foreach_concat_iter(N - 1, BASE, _N - 1)複製程式碼

到了這一步,就需要繼續展開metamacro_foreach_concat_iter


#define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG)

#define metamacro_foreach_concat_iter_(BASE, ARG) BASE ## ARG複製程式碼

這裡的2個巨集,就用到了之前說道的轉接巨集的概念,因為需要把第一個引數剔除,所以需要寫一個轉接巨集,轉換一次踢掉第一個引數。

最終完全展開就是下面的樣子:


    BASE_0 \
    SEP \
    BASE_1 \
    SEP \
    BASE_2 \
    SEP \
    BASE_3 \
     ……
     ……
     ……
     ……
     ……
     ……

    SEP \
    BASE_N - 4 \
    SEP \
    BASE_N - 3 \
    SEP \
    BASE_N - 2 \
    SEP \
    BASE_N - 1複製程式碼

metamacro_foreach_concat(BASE, SEP, ...)巨集如同它的名字一樣,把可變引數裡面每個引數都拼接到BASE後面,每個引數拼接完成之間都用SEP分隔。

試想一種場景:

如果有一連串的方法,方法名都有一個相同的字首,後面是不同的。這種場景下,利用metamacro_foreach_concat(BASE, SEP, ...)巨集是非常爽的,它會一口氣組合出相關的一列表的不同的巨集。

7. metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT)

定義如下:


#define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \
        metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT)複製程式碼

metamacro_concat 之前分析過,展開這一層:



metamacro_for_cxtN(MACRO, SEP, CONTEXT)複製程式碼

metamacro_for_cxtN的定義如下:


#define metamacro_for_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT)

#define metamacro_for_cxt2(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt1(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(1, CONTEXT)

#define metamacro_for_cxt3(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt2(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(2, CONTEXT)

#define metamacro_for_cxt4(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt3(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(3, CONTEXT)

#define metamacro_for_cxt5(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt4(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(4, CONTEXT)

#define metamacro_for_cxt6(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt5(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(5, CONTEXT)

#define metamacro_for_cxt7(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt6(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(6, CONTEXT)

#define metamacro_for_cxt8(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt7(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(7, CONTEXT)

#define metamacro_for_cxt9(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt8(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(8, CONTEXT)

#define metamacro_for_cxt10(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt9(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(9, CONTEXT)

#define metamacro_for_cxt11(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt10(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(10, CONTEXT)

#define metamacro_for_cxt12(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt11(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(11, CONTEXT)

#define metamacro_for_cxt13(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt12(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(12, CONTEXT)

#define metamacro_for_cxt14(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt13(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(13, CONTEXT)

#define metamacro_for_cxt15(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt14(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(14, CONTEXT)

#define metamacro_for_cxt16(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt15(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(15, CONTEXT)

#define metamacro_for_cxt17(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt16(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(16, CONTEXT)

#define metamacro_for_cxt18(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt17(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(17, CONTEXT)

#define metamacro_for_cxt19(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt18(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(18, CONTEXT)

#define metamacro_for_cxt20(MACRO, SEP, CONTEXT) \
    metamacro_for_cxt19(MACRO, SEP, CONTEXT) \
    SEP \
    MACRO(19, CONTEXT)複製程式碼

提取一下metamacro_for_cxtN的定義:



#define metamacro_for_cxtN(MACRO, SEP, CONTEXT) \
        metamacro_for_cxtN - 1(MACRO, SEP, CONTEXT) \
        SEP \
        MACRO(N - 1, CONTEXT)複製程式碼

把metamacro_for_cxtN完全展開如下:



    MACRO(0, CONTEXT) \
    SEP \
    MACRO(1, CONTEXT) \
    SEP \
    MACRO(2, CONTEXT) \
    SEP \
    MACRO(3, CONTEXT) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, CONTEXT) \
     SEP \
    MACRO(N - 3, CONTEXT) \
     SEP \
    MACRO(N - 2, CONTEXT) \
     SEP \
    MACRO(N - 1, CONTEXT)複製程式碼

這個巨集的用途是執行COUNT次MACRO巨集命令,每次MACRO巨集命令的第一個引數都會從COUNT開始遞減到0。

8. metamacro_head(...)

這個巨集要求它的可變引數至少為1個。


#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)複製程式碼

把巨集展開,如下:


#define metamacro_head_(FIRST, ...) FIRST複製程式碼

metamacro_head(...) 的作用就是取出可變引數列表的第一個引數。

9. metamacro_tail(...)

這個巨集要求它的可變引數至少為2個。


#define metamacro_tail(...) \
        metamacro_tail_(__VA_ARGS__)複製程式碼

把巨集展開,如下:


#define metamacro_tail_(FIRST, ...) __VA_ARGS__複製程式碼

metamacro_tail(...) 的作用就是取出可變引數列表除去第一個引數以外的所有引數。

10. metamacro_take(N, ...)

這個巨集要求它的可變引數至少有N個。



#define metamacro_take(N, ...) \
        metamacro_concat(metamacro_take, N)(__VA_ARGS__)複製程式碼

展開成如下的樣子:



metamacro_takeN(__VA_ARGS__)複製程式碼

繼續展開metamacro_takeN:


#define metamacro_take0(...)
#define metamacro_take1(...) metamacro_head(__VA_ARGS__)
#define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__))
#define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__))
#define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__))
#define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__))
#define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__))
#define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__))
#define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__))
#define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__))
#define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__))
#define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__))
#define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__))
#define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__))
#define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__))
#define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__))
#define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__))
#define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__))
#define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__))
#define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__))
#define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__))複製程式碼

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

這裡也用到了遞迴的思想,每次取完頭以後,剩下的佇列針對於此次是tail,對於下次是head。所以每次都取head,之後再遞迴的取剩下部分的head,直到取出前N個數為止。

metamacro_take(N, ...)的作用就是取出可變引數的前N個數,並把它們組合成新的引數列表。

11. metamacro_drop(N, ...)

這個巨集要求它的可變引數至少為N個。


#define metamacro_drop(N, ...) \
        metamacro_concat(metamacro_drop, N)(__VA_ARGS__)複製程式碼

展開成如下的樣子:



metamacro_dropN(__VA_ARGS__)複製程式碼

繼續展開metamacro_dropN:



#define metamacro_drop0(...) __VA_ARGS__
#define metamacro_drop1(...) metamacro_tail(__VA_ARGS__)
#define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__))
#define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__))
#define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__))
#define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__))
#define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__))
#define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__))
#define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__))
#define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__))
#define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__))
#define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__))
#define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__))
#define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__))
#define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__))
#define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__))
#define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__))
#define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__))
#define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__))
#define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__))
#define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__))複製程式碼

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

這裡也用到了遞迴的思想,每次都取當前佇列的tail,每次都丟掉當前佇列的head。這樣遞迴N次就丟掉了前N位引數。

metamacro_drop(N, ...)的作用是丟掉當前引數列表裡面的前N位引數。

12. metamacro_dec(VAL) 和 metamacro_inc(VAL)

這兩個巨集是一對。它們在超程式設計中,處理計數和index方面及其有用。VAL的值域都是[0,20]。


#define metamacro_dec(VAL) \
        metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)複製程式碼

metamacro_dec(VAL) 提供了一個被左移一位的[0,20]的序列。那麼通過metamacro_at計算出來的結果就比原來的結果小1。從而達到了減一的目的。


#define metamacro_inc(VAL) \
        metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)複製程式碼

metamacro_inc(VAL) 提供了一個被右移一位的[0,20]的序列。那麼通過metamacro_at計算出來的結果就比原來的結果大1。從而達到了加一的目的。

13. metamacro_if_eq(A, B)

首先A 和 B的值域都為[0,20],並且B要大於等於A,即0<=A<=B<=20。


#define metamacro_if_eq(A, B) \
        metamacro_concat(metamacro_if_eq, A)(B)複製程式碼

如果當A不等於0的時候,將上面的式子展開:



#define metamacro_if_eq(A, B) \
        metamacro_if_eqA(B)複製程式碼

再繼續把metamacro_if_eqA展開:



#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
#define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE))
#define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE))
#define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE))
#define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE))
#define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE))
#define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE))
#define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE))
#define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE))
#define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE))
#define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE))
#define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE))
#define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE))
#define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE))
#define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE))
#define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE))
#define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE))
#define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE))
#define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE))
#define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE))複製程式碼

上面是一個遞推的式子,最終肯定會得到metamacro_if_eq0,最終的結果就是:



metamacro_if_eq0(B - A)複製程式碼

再把metamacro_if_eq0展開:


#define metamacro_if_eq0(VALUE) \
        metamacro_concat(metamacro_if_eq0_, VALUE)複製程式碼

得到最終的展開式子:



metamacro_if_eq0_(B - A)複製程式碼

再查表得到最終結果:



#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_
#define metamacro_if_eq0_1(...) metamacro_expand_
#define metamacro_if_eq0_2(...) metamacro_expand_
#define metamacro_if_eq0_3(...) metamacro_expand_
#define metamacro_if_eq0_4(...) metamacro_expand_
#define metamacro_if_eq0_5(...) metamacro_expand_
#define metamacro_if_eq0_6(...) metamacro_expand_
#define metamacro_if_eq0_7(...) metamacro_expand_
#define metamacro_if_eq0_8(...) metamacro_expand_
#define metamacro_if_eq0_9(...) metamacro_expand_
#define metamacro_if_eq0_10(...) metamacro_expand_
#define metamacro_if_eq0_11(...) metamacro_expand_
#define metamacro_if_eq0_12(...) metamacro_expand_
#define metamacro_if_eq0_13(...) metamacro_expand_
#define metamacro_if_eq0_14(...) metamacro_expand_
#define metamacro_if_eq0_15(...) metamacro_expand_
#define metamacro_if_eq0_16(...) metamacro_expand_
#define metamacro_if_eq0_17(...) metamacro_expand_
#define metamacro_if_eq0_18(...) metamacro_expand_
#define metamacro_if_eq0_19(...) metamacro_expand_
#define metamacro_if_eq0_20(...) metamacro_expand_複製程式碼

上面這張表有兩點注意點:

  1. 除了0_0,其他的都是metamacro_expand_。

#define metamacro_consume_(...)
#define metamacro_expand_(...) __VA_ARGS__複製程式碼

除了0_0以外,其他所有操作都是直接透傳引數,什麼也不處理。metamacro_consume_(...)就是直接吞掉後續的引數。expand就是指的是可以繼續展開巨集,consume就是指的是終止展開巨集,並吃掉後面的引數。

舉2個例子:


// 第一個例子
metamacro_if_eq(0, 0)(true)(false)

// 第二個例子
metamacro_if_eq(0, 1)(true)(false)複製程式碼

直接套用最終展開式:


// 第一個例子
metamacro_if_eq0_0(true)(false)

// 第二個例子
metamacro_if_eq0_1(true)(false)複製程式碼

繼續展開:


// 第一個例子
true metamacro_consume_(false) => true

// 第二個例子
metamacro_expand_(false) => false複製程式碼

這個如果 B < A,那麼(B - A) < 0,那麼最終展開的式子就變成下面的樣子:



metamacro_if_eq0_(負數)複製程式碼

這個巨集展開到這個程度就沒法繼續下去了,就會出現編譯錯誤。

14. metamacro_if_eq_recursive(A, B)

A 和 B的值域都為[0,20],並且B要大於等於A,即0<=A<=B<=20。

定義如下:



#define metamacro_if_eq_recursive(A, B) \
        metamacro_concat(metamacro_if_eq_recursive, A)(B)複製程式碼

展開之後:


metamacro_if_eq_recursiveA(B)複製程式碼

繼續展開:


#define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE))
#define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE))複製程式碼

最終肯定會得到metamacro_if_eq_recursive0_,最終的結果就是:



metamacro_if_eq_recursive0_(B - A)複製程式碼

再把metamacro_if_eq_recursive0_展開:



#define metamacro_if_eq_recursive0(VALUE) \
    metamacro_concat(metamacro_if_eq_recursive0_, VALUE)複製程式碼

得到最終的式子:



metamacro_if_eq_recursive0_(B - A)複製程式碼

最終再比對下表:



#define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_
#define metamacro_if_eq_recursive0_1(...) metamacro_expand_
#define metamacro_if_eq_recursive0_2(...) metamacro_expand_
#define metamacro_if_eq_recursive0_3(...) metamacro_expand_
#define metamacro_if_eq_recursive0_4(...) metamacro_expand_
#define metamacro_if_eq_recursive0_5(...) metamacro_expand_
#define metamacro_if_eq_recursive0_6(...) metamacro_expand_
#define metamacro_if_eq_recursive0_7(...) metamacro_expand_
#define metamacro_if_eq_recursive0_8(...) metamacro_expand_
#define metamacro_if_eq_recursive0_9(...) metamacro_expand_
#define metamacro_if_eq_recursive0_10(...) metamacro_expand_
#define metamacro_if_eq_recursive0_11(...) metamacro_expand_
#define metamacro_if_eq_recursive0_12(...) metamacro_expand_
#define metamacro_if_eq_recursive0_13(...) metamacro_expand_
#define metamacro_if_eq_recursive0_14(...) metamacro_expand_
#define metamacro_if_eq_recursive0_15(...) metamacro_expand_
#define metamacro_if_eq_recursive0_16(...) metamacro_expand_
#define metamacro_if_eq_recursive0_17(...) metamacro_expand_
#define metamacro_if_eq_recursive0_18(...) metamacro_expand_
#define metamacro_if_eq_recursive0_19(...) metamacro_expand_
#define metamacro_if_eq_recursive0_20(...) metamacro_expand_複製程式碼

接下來就和metamacro_if_eq(A, B)巨集完全一樣了。

這個遞迴的巨集也從來沒有在RAC的其他巨集中使用,作者在這裡標註說明了這個巨集的用處。

This can be used when the former would fail due to recursive macro expansion

由於巨集在遞迴展開中可能會導致遞迴前置條件失敗,在這種情況下,應該使用這個遞迴巨集。當然,它的效果和metamacro_if_eq(A, B)巨集是完全一樣的。

15. metamacro_is_even(N)

定義如下:

N的值域在[0,20]之間。


#define metamacro_is_even(N) \
        metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1)複製程式碼

這個巨集比較簡單,就是判斷N是不是偶數,下面metamacro_at把所有從0-20的自然數是偶數的都標誌成了1,是奇數的都標誌成了0。0在這裡預設是偶數。

16. metamacro_not(B)

這裡B的取值只能是0或者1。



#define metamacro_not(B) \
        metamacro_at(B, 1, 0)複製程式碼

這個巨集很簡單,就是對引數邏輯取非運算。

三. ReactiveCocoa 中常用的巨集

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

上一章節我們分析完了ReactiveCocoa中所有的元巨集,這一章節將會把元巨集以外的巨集的實現都分析一遍。包括我們日常使用的常見的所有巨集,它們看似神祕,但是他們都是由這些元巨集來組成的。

1. weakify(...)、unsafeify(...)、strongify(...)

這三個在ReactiveCocoa一定是使用最多的,那麼就先來分析這三個。這三個巨集的定義在RACEXTScope.h中。

關於weakify(...)和strongify(...),這兩個巨集的實現分析在之前的文章裡面詳細分析過了,詳情可以看這篇文章《深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決迴圈引用》

這裡需要再次強調的一點是,在使用weakify(...)、unsafeify(...)、strongify(...)這三個巨集的前面需要額外新增@符號。原因是在這三個巨集的實現裡面都有rac_keywordify,它的實現如下:


#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif複製程式碼

不管是在什麼環境下,autoreleasepool {} 和 try {} @catch (...) {} 前面都要新增@符號,變成@autoreleasepool {} 和 @try {} @catch (...) {} 才可以繼續使用。

既然@weakify(...),@strongify(...)都分析過了,那麼這裡就分析一下@unsafeify(...)的實現。


#define unsafeify(...) \
        rac_keywordify \
        metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__)複製程式碼

rac_keywordify上面說過了,這裡就直接展開metamacro_foreach_cxt巨集。這裡就套用之前元巨集的分析,直接拿到最終展開表示式:


    MACRO(0, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1) \
    SEP \
    MACRO(2, CONTEXT, _2) \
    SEP \
    MACRO(3, CONTEXT, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, CONTEXT, _N - 4) \
     SEP \
    MACRO(N - 3, CONTEXT, _N - 3) \
     SEP \
    MACRO(N - 2, CONTEXT, _N - 2) \
     SEP \
    MACRO(N - 1, CONTEXT, _N - 1)複製程式碼

MACRO = rac_weakify_,SEP = 空格,CONTEXT = __unsafe_unretained。代入得到最終的展開式:



    rac_weakify_(0,  __unsafe_unretained, _0) \
    rac_weakify_(1,  __unsafe_unretained, _1) \
    rac_weakify_(2,  __unsafe_unretained, _2) \
    rac_weakify_(3,  __unsafe_unretained, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

    rac_weakify_(N - 4,  __unsafe_unretained, _N - 4) \
    rac_weakify_(N - 3,  __unsafe_unretained, _N - 3) \
    rac_weakify_(N - 2,  __unsafe_unretained, _N - 2) \
    rac_weakify_(N - 1,  __unsafe_unretained, _N - 1)複製程式碼

把rac_weakify_再替換掉:


#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);複製程式碼

得到最終的展開表示式:



__unsafe_unretained  __typeof__(_0) _0_weak_ = _0;
__unsafe_unretained  __typeof__(_1) _1_weak_ = _1;
__unsafe_unretained  __typeof__(_2) _2_weak_ = _2;
     ……
     ……
     ……
     ……
     ……
     ……
__unsafe_unretained  __typeof__(_N - 3) _N - 3_weak_ = _N - 3;
__unsafe_unretained  __typeof__(_N - 2) _N - 2_weak_ = _N - 2;
__unsafe_unretained  __typeof__(_N - 1) _N - 1_weak_ = _N - 1複製程式碼

其中 _0, _1, _2 …… _N - 3, _N - 2, N - 1是 \_VA_ARGS__裡面對應的是0 - N的引數值。

2. RACTuplePack(...) 和 RACTupleUnpack(...)

這兩個在ReactiveCocoa中也是非常常見的巨集,專門用在RACTuple中。

先看RACTuplePack(...)


#define RACTuplePack(...) \
        RACTuplePack_(__VA_ARGS__)複製程式碼

再展開一步:


#define RACTuplePack_(...) \
        ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])複製程式碼

這裡呼叫了RACTuple的tupleWithObjectsFromArray:方法。主要需要展開的是:


metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__)複製程式碼

直接呼叫上一章節中metamacro_foreach的最終表示式:



    MACRO(0, _0) \
    SEP \
    MACRO(1, _1) \
    SEP \
    MACRO(2, _2) \
    SEP \
    MACRO(3, _3) \
     ……
     ……
     ……
     ……
     ……
     ……

     SEP \
    MACRO(N - 4, _N - 4) \
     SEP \
    MACRO(N - 3, _N - 3) \
     SEP \
    MACRO(N - 2, _N - 2) \
     SEP \
    MACRO(N - 1, _N - 1)複製程式碼

MACRO = RACTuplePack_object_or_ractuplenil , SEP = 空格,替換之後如下:


RACTuplePack_object_or_ractuplenil(0, _0) \
RACTuplePack_object_or_ractuplenil(1, _1) \
RACTuplePack_object_or_ractuplenil(2, _2) \
     ……
     ……
     ……
     ……
     ……
     ……
RACTuplePack_object_or_ractuplenil( N - 3, _N - 3) \
RACTuplePack_object_or_ractuplenil( N - 2, _N - 2) \
RACTuplePack_object_or_ractuplenil( N - 1, _N - 1)複製程式碼

最後一步就是替換掉RACTuplePack_object_or_ractuplenil:


#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \
    (ARG) ?: RACTupleNil.tupleNil,複製程式碼

注意這裡巨集結尾是“,”逗號,而不是“;”分號,原因是因為tupleWithObjectsFromArray:方法裡面是各個元素,所以這裡用“;”分號就會出錯,反而應該用“,”逗號,可見設計巨集的時候需要考慮清楚使用場景,不能亂寫。

展開上面最後一層巨集之後,原可變引數列表裡面的所有非nil的值就都排列到了tupleWithObjectsFromArray:方法裡面了,如果是nil的,就會變成RACTupleNil.tupleNil放進Array裡面。

再來看看RACTupleUnpack(...)


#define RACTupleUnpack(...) \
        RACTupleUnpack_(__VA_ARGS__)複製程式碼

再展開一步:


#define RACTupleUnpack_(...) \
    metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \
    \
    int RACTupleUnpack_state = 0; \
    \
    RACTupleUnpack_after: \
        ; \
        metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \
        if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \
        \
        while (RACTupleUnpack_state != 2) \
            if (RACTupleUnpack_state == 1) { \
                goto RACTupleUnpack_after; \
            } else \
                for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \
                    [RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ]複製程式碼

乍一看這個巨集像一段程式,仔細分析一下也不難。RACTupleUnpack_state 就是一個區域性變數,代表狀態的。RACTupleUnpack_after: 這是一個標號,用來給goto跳轉使用的。


// 1
metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) 
// 2
metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__)
// 3
metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__)複製程式碼

這裡面需要展開的就是這3個巨集了。

套用上一章節中metamacro_foreach的最終表示式,直接把MACRO分別為 RACTupleUnpack_decl,RACTupleUnpack_assign,RACTupleUnpack_value 代入表示式。


// 1
RACTupleUnpack_decl(0, _0) \
     ……
     ……
     ……
RACTupleUnpack_decl( N - 1, _N - 1)
// 2
RACTupleUnpack_assign(0, _0) \
     ……
     ……
     ……
RACTupleUnpack_assign( N - 1, _N - 1)
// 3
RACTupleUnpack_value(0, _0) \
     ……
     ……
     ……
RACTupleUnpack_value( N - 1, _N - 1)複製程式碼

分別替換掉這3個巨集:


#define RACTupleUnpack_decl(INDEX, ARG) \
    __strong id RACTupleUnpack_decl_name(INDEX);

#define RACTupleUnpack_assign(INDEX, ARG) \
    __strong ARG = RACTupleUnpack_decl_name(INDEX);

#define RACTupleUnpack_value(INDEX, ARG) \
    [NSValue valueWithPointer:&RACTupleUnpack_decl_name(INDEX)],複製程式碼

發現這3個巨集都是用RACTupleUnpack_decl_name實現的。


#define RACTupleUnpack_decl_name(INDEX) \
        metamacro_concat(metamacro_concat(RACTupleUnpack, __LINE__), metamacro_concat(_var, INDEX))複製程式碼

這個展開就是一個名字:


RACTupleUnpack __LINE__ _varINDEX複製程式碼

之後的實現,請看《ReactiveCocoa 中 集合類RACSequence 和 RACTuple底層實現分析》這篇文章的詳細分析。

3. RACObserve(TARGET, KEYPATH)

定義如下:


#define RACObserve(TARGET, KEYPATH) \
    ({ \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
        __weak id target_ = (TARGET); \
        [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
        _Pragma("clang diagnostic pop") \
    })複製程式碼

看完定義,RACObserve(TARGET, KEYPATH) 實質其實就是呼叫了rac_valuesForKeyPath:方法。這個方法是NSObject的一個category,所以只要是NSObject就可以呼叫這個方法。所以這裡的關鍵就是要分析清楚@keypath(TARGET, KEYPATH) 這個巨集的實現。

以下重點分析一下keypath(...)的實現


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

#define keypath1(PATH) \
    (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

#define keypath2(OBJ, PATH) \
    (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))複製程式碼

metamacro_argcount這個巨集在元巨集裡面分析過,是取出可變引數個數的。metamacro_if_eq也詳細分析過,是判斷裡面2個引數是否相當的。所以keypath(...)總體展開的意思是說,可變引數的個數是否等於1,如果等於1,就執行(keypath1(__VA_ARGS__)),如果不等於1,就執行(keypath2(__VA_ARGS__))。

這裡有幾點需要說明的:

1.加void是為了防止逗號表示式的warning。例如:


int a=0; int b = 1;
int c = (a,b);複製程式碼

由於a沒有被用到,所以會有警告。但是寫成如下的樣子就不會出現警告了:


int c = ((void)a,b);複製程式碼

所以上面keypath1和keypath2加了幾個void就是為了防止出現warning。

2.加NO是C語言判斷條件短路表示式。增加NO && 以後,預編譯的時候看見了NO,就會很快的跳過判斷條件。

3.strchr函式原型如下:


extern char *strchr(const char *s,char c);複製程式碼

查詢字串s中首次出現字元c的位置。返回首次出現字元c的位置的指標,返回的地址是被查詢字串指標開始的第一個與字元c相同字元的指標,如果字串中不存在字元c則返回NULL。

4.當輸入self.的時候,會出現編譯器的語法提示,原因是OBJ.PATH,因為這裡的點,所以輸入第二個引數時編輯器會給出正確的程式碼提示。

5.使用keypath(...)的時候前面會加上@符號,原因是經過keypath1(PATH)和keypath2(OBJ, PATH)之後出現的結果是一個C的字串,前面加上@以後,就變成了OC的字串了。

舉3個例子




// 例子1,一個引數的情況,會呼叫keypath1(PATH)
NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// 輸出=> @"lowercaseString.UTF8String"


// 例子2,2個引數的情況,支援自省
NSString *versionPath = @keypath(NSObject, version);
//  輸出=> @"version"

// 例子3,2個引數的情況
NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
// 輸出=> @"lowercaseString"複製程式碼

相應的也有集合類的keypath


#define collectionKeypath(...) \
    metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__))

#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])

#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])複製程式碼

原理也是呼叫了keypath(PATH),原理這裡就不再贅述了。

4. RAC(TARGET, ...)

巨集定義如下:


#define RAC(TARGET, ...) \
        metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))複製程式碼

RAC(TARGET, ...) 和上一個RACObserve(TARGET, KEYPATH)原理類似。如果只有一個引數就呼叫(RAC_(TARGET, __VA_ARGS__, nil)),如果是多個引數就呼叫(RAC_(TARGET, __VA_ARGS__))。



#define RAC_(TARGET, KEYPATH, NILVALUE) \
        [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)]複製程式碼

到這裡就很明瞭了,其實內部就是呼叫RACSubscriptingAssignmentTrampoline類的initWithTarget: nilValue:方法。

我們都知道RAC(TARGET, ...)巨集是用來把一個訊號繫結給一個物件的屬性,繫結之後,每次訊號傳送出一個新的值,就會自動設定到執行的keypath中。當訊號完成之後,這次繫結也會自動的解除。

RAC_(TARGET, KEYPATH, NILVALUE) 會把訊號繫結到TARGET指定的KEYPATH上。如果訊號傳送了nil的值,那麼會替換成NILVALUE賦值給對應的屬性值上。

RAC_(TARGET, __VA_ARGS__)只不過是RAC_(TARGET, KEYPATH, NILVALUE)第三個引數為nil。

5. RACChannelTo(TARGET, ...)

RACChannelTo(TARGET, ...)這個巨集完全可以類比RAC(TARGET, ...),兩個幾乎完全一樣。



#define RACChannelTo(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RACChannelTo_(TARGET, __VA_ARGS__, nil)) \
        (RACChannelTo_(TARGET, __VA_ARGS__))複製程式碼

如果只有一個引數就呼叫(RACChannelTo_(TARGET, __VA_ARGS__, nil)) ,如果是多個引數就呼叫(RACChannelTo_(TARGET, __VA_ARGS__))。(RACChannelTo_(TARGET, __VA_ARGS__))相當於是(RACChannelTo_(TARGET, __VA_ARGS__, nil)) 第三個引數傳了nil。



#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \
    [[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]複製程式碼

最終內部是呼叫了RACKVOChannel的initWithTarget: keyPath: nilValue:方法。具體原理可以完全類比RAC(TARGET, ...)巨集展開,這裡不再贅述。

平時我們都是這樣用:


   RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty);
   RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10);複製程式碼

6. onExit

巨集定義如下:


#define onExit \
    rac_keywordify \
    __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^複製程式碼

由於rac_keywordify的存在,所以在使用onExit的時候,前面也要加上@符號。

這個巨集比較特殊,最後是跟著一個閉包,比如這樣:


        @onExit {
              free(attributes);
        };

        @onExit {
                [objectLock unlock];
            };複製程式碼

@onExit定義當前程式碼段退出時要執行的一些程式碼。程式碼必須用大括號括起來並以分號結尾,無論是何種情況(包括出現異常,goto語句,return語句,break語句,continue語句)下跳出程式碼段,都會執行onExit後面的程式碼。

@onExit提供的程式碼被放進一個block塊中,之後才會執行。因為在閉包中,所以它也必須遵循記憶體管理方面的規則。@onExit是以一種合理的方式提前退出清理塊。

在相同程式碼段中如果有多個@onExit語句,那麼他們是按照反字典序的順序執行的。

@onExit語句不能在沒有大括號的範圍內使用。在實際使用過程中,這不是一個問題,因為@onExit後面如果沒有大括號,那麼它是一個無用的結構,不會有任何事情發生。

最後

ReactiveCocoa 中 奇妙無比的 “巨集” 魔法

關於ReactiveCocoa裡面所有巨集的實現分析都已經分析完成。我覺得巨集是對一段邏輯的高度抽象,當一個巨集被思維完備的開發人員設計出來以後,就是一個充滿神奇色彩的魔法!如果能把一些簡單實用的功能或者邏輯抽象成巨集,把這些時間都節約到預編譯中,節約執行時的時間,單從編碼的程度來說,都是極有樂趣的一件事情!如果以後有機會,希望還能和大家交流交流Lisp裡面的相關巨集魔法的知識。

相關文章