ReactiveObjC-从RAC宏说起
这里说的ReactiveObjC,就是ReactiveCocoa的Objective-C版本:
https://github.com/ReactiveCocoa/ReactiveObjC
从一个小例子开始
下面的代码,实现的效果是,当用户名输入框及密码输入框都有内容时,登录按钮才会变得可用,否则,不可用。
1  |  | 
以上代码在经过预处理后,会被转化为:
1  |  | 
将其中的[@(((void)(__objc_no && ((void)self.loginBtn.enabled, __objc_no)), "enabled"))]简化一下,就变成了:
1  |  | 
上面的代码实际上就是调用RACSubscriptingAssignmentTrampoline中的- setObject:forKeyedSubscript:方法:
1  |  | 
为了弄明白,上面的代码是如何展开的,就需要厘清RAC宏。
RAC宏
在厘清RAC宏之前,需要先弄明白其所涉及的所有宏定义。
先来看看它们是如何被定义的。
所有涉及的宏

在上面的图中,可以清晰地看出每个宏的定义,及其实现所依赖的宏。『请在新标签页或新窗口中打开图像,以查看高清图』
宏定义释疑
| 宏 | 描述 | 
|---|---|
metamacro_concat_(A, B) | 
以字符串的形式连接A与B。示例:metamacro_concat_(1, 2),其结果为12 | 
metamacro_concat(A, B) | 
同上 | 
keypath1(PATH) | 
在它的实现中,逗号表达式中最前面的NO,会在运行时,导致&&之后的((void)PATH, NO))不会执行,节省了开销。示例:keypath1(self.enabled),结果为(((void)(NO && ((void)self.enabled, NO)), strchr(# self.enabled, '.') + 1)),也就是(((void)(NO && ((void)self.enabled, NO)), strchr("self.enabled", '.') + 1)),(((void)(NO && ((void)self.enabled, NO)), ".enabled" + 1)),(NO, "enabled"),"enabled"。注意,这里的PATH,必须要有『.』,否则在运行的时候会导致崩溃:比如keypath1(enabled),会变成(((void)(NO && ((void)enabled, NO)), strchr(# enabled, '.') + 1)),导致找不到『.』,strchr(# enabled, '.')的结果为NULL,最终结果为NULL + 1,使用时会导致出现EXC_BAD_ACCESS | 
keypath2(OBJ, PATH) | 
在它的实现中,OBJ.PATH,会在编译期,进行相关的校验,如果OBJ没有对应的PATH,会出现编译错误;逗号表达式中最前面的NO,会在运行时,导致&&之后的((void)OBJ.PATH, NO))不会执行,节省了开销。示例:keypath2(self.loginBtn, enabled),结果为(((void)(NO && ((void)self.loginBtn.enabled, NO)), # enabled)),也就是(NO, "enabled"),"enabled" | 
keypath(...) | 
会在编译期,对key path进行校验。返回由可变参数构成的key path。示例:keypath(self.loginBtn, enabled),结果为"enabled" | 
metamacro_at(N, ...) | 
返回索引为N的可变参数(索引以0开始)。必须至少提供N + 1个可变参数,N为区间[0, 20]中的整数。其展开后就是metamacro_atN(...),这里的N就是区间[0, 20]中的一个整数。示例:metamacro_at(3, 1, 3, 5, 7),因为第一个参数为3,因此必须至少提供4个可变参数(这里的可变参数1、3、5、7,总数量为4),其中,7的索引为3,因此,结果为7。来看看为什么结果是7:把metamacro_at(3, 1, 3, 5, 7)展开后就是metamacro_concat(metamacro_at, 3)(__VA_ARGS__) ,其中metamacro_concat(metamacro_at, 3)的结果为metamacro_at3,所以结果为metamacro_at3(1, 3, 5, 7)。在metamacros.h文件中,有20个metamacro_at扩展,分别是metamacro_at1、metamacro_at2 … metamacro_at19、metamacro_at20,它们都使用了metamacro_head,其定义为#define metamacro_head(...) metamacro_head_(__VA_ARGS__, 0),metamacro_head_的定义是#define metamacro_head_(FIRST, ...) FIRST。再看看metamacro_at3的定义:#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__),因此将metamacro_at3(1, 3, 5, 7)一步步展开,就是metamacro_head(7),metamacro_head_(7, 0),结果就是7 | 
metamacro_argcount(...) | 
返回所传入的参数的总个数。必须至少提供1个参数。示例:metamacro_argcount(1),展开后就是metamacro_at(20, 1, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),其结果为索引为20的可变参数,也就是1(注意,metamacro_at的第一个参数20,并不属于可变参数的一部分) | 
metamacro_if_eq(A, B) | 
检查A、B是否相等,如果相等,就展开后面的第一个参数列表,否则就展开后面的第二个参数列表 | 
RAC_(TARGET, KEYPATH, NILVALUE) | 
展开后就是[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)]。示例:RAC_(self.loginBtn, enabled, nil),其结果就是[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.loginBtn) nilValue:(nil)][@keypath(self.loginBtn, enabled)],[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.loginBtn) nilValue:(nil)][@"enabled"] | 
RAC(TARGET, ...) | 
因为其使用了metamacro_if_eq,因此,不是展开(RAC_(TARGET, __VA_ARGS__, nil)),就是展开(RAC_(TARGET, __VA_ARGS__))。其可变参数的个数,只能是1或2,如果是1个可变参数,就对应KEYPATH;如果是2个可变参数,就对应KEYPATH、NILVALUE | 
ReactiveObjC-从RAC宏说起
      https://daniate.github.io/2016/10/31/ReactiveObjC-从RAC宏说起/