美文网首页
RAC宏定义

RAC宏定义

作者: 高思阳 | 来源:发表于2019-05-14 15:55 被阅读0次

    这里说的ReactiveObjC,就是ReactiveCocoaObjective-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_at1metamacro_at2 ... metamacro_at19metamacro_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个可变参数,就对应KEYPATHNILVALUE

    转载:https://daniate.com/2016/10/31/ReactiveObjC-%E4%BB%8ERAC%E5%AE%8F%E8%AF%B4%E8%B5%B7.html

    相关文章

      网友评论

          本文标题:RAC宏定义

          本文链接:https://www.haomeiwen.com/subject/jozsaqtx.html