在RAC中有一個(gè)「keypath」宏定義,我們一般使用它來將對(duì)象的某個(gè)屬性轉(zhuǎn)換成字符串,它的亮點(diǎn)在于帶上了編譯檢查的功能,避免直接使用字符串容易導(dǎo)致的拼寫錯(cuò)誤危融。
下面我們來分析一下這個(gè)宏是怎么實(shí)現(xiàn)的。
#define keypath(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
這是「keypath」的定義雷袋,先看前半部分「metamacro_if_eq(1, metamacro_argcount(VA_ARGS))」的實(shí)現(xiàn)吉殃。
「metamacro_if_eq」的實(shí)現(xiàn)比較簡單,點(diǎn)進(jìn)去看它的定義:
#define metamacro_if_eq(A, B) \
metamacro_concat(metamacro_if_eq, A)(B)
「metamacro_concat」它的作用是將參數(shù)A和B拼接起來楷怒,因此「metamacro_if_eq(1, metamacro_argcount(VA_ARGS))」會(huì)轉(zhuǎn)換成:
接下來我們分析一下「metamacro_argcount」的實(shí)現(xiàn):
#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)
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
「metamacro_at」里也使用了「metamacro_concat」宏蛋勺,經(jīng)過轉(zhuǎn)換,「metamacro_argcount(VA_ARGS)」轉(zhuǎn)換成:
metamacro_at20(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
metamacro_at20」的作用是計(jì)算「VA_ARGS」接收的參數(shù)個(gè)數(shù)鸠删。
由此我們可知抱完,「metamacro_argcount」用于計(jì)算傳入「keypath」的參數(shù)個(gè)數(shù)。
接下來刃泡,我們回去分析「metamacro_if_eq1」的實(shí)現(xiàn):
#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
#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」的實(shí)現(xiàn)巧娱,它也使用了「metamacro_at」,經(jīng)過轉(zhuǎn)換「metamacro_dec(VALUE)」變成:
metamacro_atVALUE(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) # VALUE為前面計(jì)算的參數(shù)個(gè)數(shù)
「metamacro_atVALUE」系列的定義為:(比較多烘贴,只列出了前三個(gè)禁添,順便貼上了「metamacro_head」和「metamacro_head_」的定義)
#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_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST
「metamacro_atVALUE」系列的宏的參數(shù)個(gè)數(shù)隨著「VALUE」值的增加而增多,很明顯桨踪,「_0」老翘、「_1」這些參數(shù)是用來占位的,余下的參數(shù)會(huì)被傳入「metamacro_head」中。
比如VALUE的值為1酪捡,那么「metamacro_at1(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)」就被轉(zhuǎn)換為:
metamacro_head(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
而「metamacro_head」只是將參數(shù)直接傳給「metamacro_head_」,「metamacro_head_」的作用也比較明顯:取出第一個(gè)參數(shù)纳账。因此逛薇,上面的宏被轉(zhuǎn)換成:「0」。
由此可知疏虫,「metamacro_dec」的作用是讓參數(shù)自減1.
PS:「metamacro_head」還有一個(gè)參數(shù)「0」永罚,它是為了保證「metamacro_dec」轉(zhuǎn)換的結(jié)果>=0。
回到代碼1的分析卧秘,我們接下來要分析的是「metamacro_if_eq0」:
#define metamacro_if_eq0(VALUE) \
metamacro_concat(metamacro_if_eq0_, VALUE)
#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_
#define metamacro_if_eq0_1(...) metamacro_expand_
#define metamacro_consume_(...)
#define metamacro_expand_(...) __VA_ARGS__
「metamacro_if_eq0」直接轉(zhuǎn)換成「metamacro_if_eq0_VALUE」系列宏(只列出了前兩個(gè))呢袱。
這個(gè)宏是一個(gè)條件判斷,如果傳入「keypath」的參數(shù)個(gè)數(shù)為1翅敌,則執(zhí)行「keypath1」羞福,如果大于1,則執(zhí)行「keypath2」蚯涮。它是怎么做到的呢治专?我們來分析一下:
經(jīng)過上面一系列的轉(zhuǎn)換以后,我們得到下面這個(gè)宏:
metamacro_if_eq0_VALUE(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
當(dāng)「VALUE」的值為0時(shí)遭顶,上面的宏轉(zhuǎn)換成:
keypath1(__VA_ARGS__) metamacro_consume_(keypath2(__VA_ARGS__)) -> keypath1(__VA_ARGS__) # 「metamacro_consume_」宏沒有作為替換的字符串张峰,相當(dāng)于直接去掉
當(dāng)「VALUE」的值為1時(shí),上面的宏就轉(zhuǎn)換成了:
metamacro_expand_(keypath2(__VA_ARGS__)) -> keypath2(__VA_ARGS__)
到這里我們就剩下最后兩個(gè)宏了棒旗,繼續(xù)分析喘批!
#define keypath1(PATH) \
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
1)這里運(yùn)用了逗號(hào)表達(dá)式,從左往右逐個(gè)執(zhí)行表達(dá)式铣揉,最后一個(gè)表達(dá)式作為整個(gè)表達(dá)式的返回值饶深。最后一個(gè)表達(dá)式的strchr函數(shù)用于查找參數(shù)一字符串中首次出現(xiàn)參數(shù)二字符的位置,返回值為指向該位置的指針逛拱≈嘞玻「#」可將后面的參數(shù)轉(zhuǎn)換成字符串,作為strchr函數(shù)的第一個(gè)參數(shù)橘券,因此「strchr(# PATH, '.') + 1」的返回值是指向「PATH」字符串中第一次出現(xiàn) '.' 字符的后一個(gè)字符的指針额湘。如:「PATH」為'self.object',則返回的指針指向 'o' 旁舰,指針打印出來為:「"object"」锋华。
2)strchr函數(shù)的返回值是一個(gè)字符指針,而我們要的是一個(gè)NSString對(duì)象箭窜,它是怎么將一個(gè)字符指針轉(zhuǎn)換成NSString對(duì)象的呢毯焕?答案在于最外層的括號(hào),簡化一下「@keypath1(PATH)」的轉(zhuǎn)換結(jié)果:@(strchr(# PATH, '.') + 1),起轉(zhuǎn)換作用的其實(shí)就是「@()」了纳猫,它能將一個(gè)字符指針轉(zhuǎn)換成NSString對(duì)象婆咸。以前也不知道能這么干,漲姿勢了...
3)只需要「strchr(# PATH, '.') + 1」就可以將PATH轉(zhuǎn)換成目標(biāo)字符串了芜辕,那為什么還需要前面這個(gè)表達(dá)式「((void)(NO && ((void)PATH, NO))」呢尚骄?
其實(shí)前面這個(gè)表達(dá)式的作用是給「@keypath」添加編譯檢查和代碼提示功能,它將「PATH」作為表達(dá)式的一部分侵续,Xcode會(huì)為表達(dá)式自動(dòng)提示倔丈。簡化一下相當(dāng)于:(PATH,strchr(# PATH, '.') + 1))状蜗,前面的PATH雖然對(duì)整個(gè)表達(dá)式的返回結(jié)果沒有影響需五,但Xcode會(huì)為它提供編譯檢查和代碼提示功能。
4)「void」的作用是為了防止逗號(hào)表達(dá)式的警告轧坎。如:
int a = 0, b = 1;
int c = (a, b); # 由于變量a沒有被使用宏邮,所以會(huì)有unused的警告
# 給它加個(gè)void就不會(huì)有警告了
int c = ((void)a, b);
5)加「NO &&」是為了條件短路,預(yù)編譯的時(shí)候遇到它會(huì)直接跳過「&&」后面的表達(dá)式缸血。
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
這個(gè)宏跟「keypath1」比較類似蜀铲,下面分析一下不同的地方:
1)它接收兩個(gè)參數(shù),「# PATH」把PATH參數(shù)直接轉(zhuǎn)換成字符串属百,作為整個(gè)表達(dá)式的返回值记劝。簡化一下「@keypath2(OBJ, PATH)」的轉(zhuǎn)換結(jié)果:@("PATH")。
2)當(dāng)你輸入第二個(gè)參數(shù)的時(shí)候族扰,你會(huì)發(fā)現(xiàn)只能輸入OBJ的屬性厌丑,這是因?yàn)椤窸BJ.PATH」里的「.」,給「PATH」提供了編譯檢查渔呵。
到這里我們就分析完了怒竿,通過以上的分析我們也可以得出「@keypath」的作用:
參數(shù)為一個(gè)的時(shí)候,@keypath(self.object) → @"object"
參數(shù)為兩個(gè)的時(shí)候扩氢,@keypath(self, object) → @"object"