RAC(ReactiveCocoa)介紹(十一)——RAC宏定義

在編程領域里的宏是一種抽象(Abstraction),它根據一系列預定義的規則替換一定的文本模式。解釋器在遇到宏時會自動進行這一模式替換。絕大多數情況下,“宏”這個詞的使用暗示著將小命令或動作轉化為一系列指令。
在RAC框架中,其宏定義的功能強大能幫助開發者更加快速、便捷地進行開發工作。常用的比如:打破循環引用、以及KVO方法的屬性監聽等等。

打破實例變量的循環引用

KVO屬性監聽

這一篇主要探究RAC中的宏定義強大之處究竟在哪。
首先來看下最常用的@weakify(self)
weakify(...)實現

此處注意,反斜杠\的作用是作為連接符使用,將代碼進行連接。即使用weakify(...)宏定義時,將先后執行 rac_keywordifymetamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) 代碼。
先來看下rac_keywordify代碼的作用:

#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

在debug環境下,只有一句autoreleasepool {},此代碼是增強代碼的編譯能力,至于為何要如此使用?在經常使用的宏定義RACObserve(TARGET, KEYPATH)觀察KVO屬性時,能夠在KEYPATH中,代碼預提示出指定TARGET中的屬性

RACObserve能夠提示出當前self中存在的實例變量

metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)代碼實現:

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \ 
 metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
//此函數中,分別將rac_weakify_傳入MACRO參數,(空格)傳入SEP,__weak傳入CONTEXT,__VA_ARGS_(可變參數)傳入...

metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)函數中,實現了metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

首先來看看第二個參數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)
#define metamacro_at(N, ...) \
//此處將20傳入N參數中,其余的作為可變參數傳入
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)
#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B

在Objective-C語言中方法調用底層實際上是對C的消息轉發,Objective-C語言最終要編譯成C函數語言,接觸過runtime之后會更加明白。比如在runtime中最常用的objc_msgSend方法,Objective-C函數調用都要通過它來進行消息發送實現,objc_msgSend(id self, SEL _cmd, arg),在Objective-C中,該消息發送方法默認實現了id類型的self以及方法選擇器_cmd,arg參數為開發者自定義的內容。

宏定義在Objective-C中使用#define,在C中使用define,而#是Objective-C區別于C語言的存在。那么#define metamacro_concat_(A, B) A ## B從Objective-C環境編譯為C語言時,最終實現的是AB,也就意味著將A、B拼接到一起。上述宏定義最終是將metamacro_at參數與20參數拼接到一起,組成metamacro_at20(__VA_ARGS__)。拼接完成之后可以找到metamacro_at20的宏定義

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

而且此處會發現,從0-20都已存在宏定義

metamacro_atN宏定義

metamacro_atN的宏定義,意思為截取掉宏定義中前N個元素,保留剩下的元素傳入至metamacro_head(__VA_ARGS__)
展開metamacro_head(__VA_ARGS__),可以發現下列宏定義

#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST

意味著要取截取后剩下元素中的第一個元素,而這個元素的值也就是metamacro_argcount(...)宏返回出來的元素個數。
其實現原理為20 -( 20 - n )= n,metamacro_argcount(...) 宏就是這樣在預編譯時期獲取到參數個數的。

為了更方便理解metamacro_argcount(...) 宏實現的過程,舉個例子:
metamacro_argcount(self,(NSString *)str)計算出個數為2的過程。
metamacro_argcount(...)宏展開后變為:

//宏里的可變參數個數為22個
metamacro_at(20, self, str, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
//合并后變為metamacro_at20(...)宏
metamacro_at20(self, str, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

而metamacro_at20的宏定義為

#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__)
//metamacro_head(...)的可變參數即為截取后剩下的2個元素(2,1)
metamacro_head(2,1)

在metamacro_at20宏中,前20個元素位置已被預設好的元素占用,那么metamacro_head(...)的可變參數即為截取后剩下的2個元素(2,1)。通過metamacro_head宏取出第一個元素的值并返回,最后得到的數值為2,傳入參數的個數為2。這也就是在預編譯時如何獲取傳參個數的全過程。

這時,再回到metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)宏,現在已經知道metamacro_concat是用來生成一個新的宏定義,此處就變為metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)
注:此處cxtN中的N為metamacro_argcount(...)宏返回的個數。

metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)宏的實現,N同樣為0-20

繼續拿上面的例子來說,當返回為2個元素個數之后
在宏最外部分別將rac_weakify_傳入MACRO參數,(空格)傳入SEP,__weak傳入CONTEXT

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)
    //展開后變為
    rac_weakify_(0, __weak, self)  \
    rac_weakify_(1, __weak, str)

此時,得到了一個rac_weakify_(...)宏,那么來看下這個宏什么作用

#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

展開后

__weak __typeof__(self)  self_weak_ = (self);
__weak __typeof__(str)  str_weak_ = (str);

。。。。。。。。。分析了這么一大圈,就是為了一句弱引用???




不過這樣也有好處,就是可以最多傳入20個對象全部弱引用

既然已經分析出了@weakify(...)宏定義的作用,那么@strongify(...)宏定義作用也就顯而易見了:將對象變為強引用。

#define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
//最后的展開結果
__weak __typeof__(self_weak_)  self = (self_weak_);
__weak __typeof__(str_weak_)  str = (str_weak_);

這也說明了在RAC中@weakify(...)宏定義與@strongify(...)一定要搭配使用的原因。
為什么要在這里加一個@符號?
Objective-C源于C語言,輸入字符串時,C語言用""來表示,而Objective-C是用@""來表示。此處要加@符號,是把C語言的結構包裝成Objective-C。添加@符號,作用為預編譯,會從底層C語言中找相應的函數,尋找TARGET相應的KEYPATH路徑。

接下來,來分析一下RAC中觀察屬性KVO宏定義RACObserve(TARGET, KEYPATH)

#define _RACObserve(TARGET, KEYPATH) \
({ \
    __weak id target_ = (TARGET); \
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})

將TARGET弱引用生成一個target_對象,下面主要來分析下一句代碼
先查看@keypath(TARGET, KEYPATH)

#define keypath(...) \
//判斷可變參數個數是否為1
//若為1則執行(keypath1(__VA_ARGS__))
//否則執行(keypath2(__VA_ARGS__))
    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))

此處@keypath(TARGET, KEYPATH)一定要添加@符號,就是為了能預編譯出TARGET中所有的KEYPATH屬性。
何為預編譯?
在main函數執行之前,執行預編譯處理。把整個類加載進內存中,在編程過程中,會去匹配TARGET類的類型,當匹配到對應類之后,會去編譯查找對應的屬性表property list、成員表IRG list、方法表method list。所以這里執行了預編譯處理后,就可以提示出TARGET所有的示例變量、屬性以及方法。


該文章首次發表在 簡書:我只不過是出來寫寫代碼 博客,并自動同步至 騰訊云:我只不過是出來寫寫iOS 博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容