深入研究RAC的@keypath

在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"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耕驰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子录豺,更是在濱河造成了極大的恐慌朦肘,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件双饥,死亡現(xiàn)場離奇詭異媒抠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咏花,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門趴生,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事苍匆×跫保” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵浸踩,是天一觀的道長叔汁。 經(jīng)常有香客問我,道長民轴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任球订,我火速辦了婚禮后裸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冒滩。我一直安慰自己微驶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布开睡。 她就那樣靜靜地躺著因苹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪篇恒。 梳的紋絲不亂的頭發(fā)上扶檐,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音胁艰,去河邊找鬼款筑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腾么,可吹牛的內(nèi)容都是我干的奈梳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼解虱,長吁一口氣:“原來是場噩夢啊……” “哼攘须!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起殴泰,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤于宙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后悍汛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體限煞,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年员凝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了署驻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖旺上,靈堂內(nèi)的尸體忽然破棺而出瓶蚂,到底是詐尸還是另有隱情,我是刑警寧澤宣吱,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布窃这,位于F島的核電站,受9級(jí)特大地震影響征候,放射性物質(zhì)發(fā)生泄漏杭攻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一疤坝、第九天 我趴在偏房一處隱蔽的房頂上張望兆解。 院中可真熱鬧,春花似錦跑揉、人聲如沸锅睛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽现拒。三九已至,卻和暖如春望侈,著一層夾襖步出監(jiān)牢的瞬間印蔬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工脱衙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扛点,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓岂丘,卻偏偏與公主長得像陵究,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奥帘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容