这里说的ReactiveObjC
,就是ReactiveCocoa
的Objective-C
版本:
https://github.com/ReactiveCocoa/ReactiveObjC
从一个小例子开始
下面的代码,实现的效果是,当用户名输入框
及密码输入框
都有内容时,登录按钮
才会变得可用,否则,不可用。
NSArray *signals = @[
self.usernameTF.rac_textSignal,
self.passwordTF.rac_textSignal,
];
RAC(self.loginBtn, enabled) = [RACSignal combineLatest:signals reduce:^id _Nonnull (NSString *username, NSString *password) {
username = [username stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
password = [password stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return @(username.length > 0 && password.length > 0);
}];
以上代码在经过预处理后,会被转化为:
NSArray *signals = @[
self.usernameTF.rac_textSignal,
self.passwordTF.rac_textSignal,
];
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.loginBtn) nilValue:(((void *)0))][@(((void)(__objc_no && ((void)self.loginBtn.enabled, __objc_no)), "enabled"))] = [RACSignal combineLatest:signals reduce:^id _Nonnull (NSString *username, NSString *password) {
username = [username stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
password = [password stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return @(username.length > 0 && password.length > 0);
}];
将其中的[@(((void)(__objc_no && ((void)self.loginBtn.enabled, __objc_no)), "enabled"))]
简化一下,就变成了:
NSArray *signals = @[
self.usernameTF.rac_textSignal,
self.passwordTF.rac_textSignal,
];
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.loginBtn) nilValue:(((void *)0))][@"enabled"] = [RACSignal combineLatest:signals reduce:^id _Nonnull (NSString *username, NSString *password) {
username = [username stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
password = [password stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return @(username.length > 0 && password.length > 0);
}];
上面的代码实际上就是调用RACSubscriptingAssignmentTrampoline
中的- setObject:forKeyedSubscript:
方法:
[[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self.loginBtn) nilValue:((void *)0)] setObject:[RACSignal combineLatest:signals reduce:^id _Nonnull (NSString *username, NSString *password) {
username = [username stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
password = [password stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return @(username.length > 0 && password.length > 0);
}] forKeyedSubscript:@"enabled"];
为了弄明白,上面的代码是如何展开的,就需要厘清RAC
宏。
RAC宏
在厘清RAC
宏之前,需要先弄明白其所涉及的所有宏定义。
先来看看它们是如何被定义的。
所有涉及的宏
RAC.png在上面的图中,可以清晰地看出每个宏的定义,及其实现所依赖的宏。『请在新标签页
或新窗口
中打开图像,以查看高清图』
宏定义释疑
宏 | 描述 |
---|---|
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
|
转载:https://daniate.com/2016/10/31/ReactiveObjC-%E4%BB%8ERAC%E5%AE%8F%E8%AF%B4%E8%B5%B7.html
网友评论