ReactiveCocoa 学习 一

ReactiveCocoa 框架,在刚听说过这个框架时,我便在github上搜索了一下,star的数量确实让我震惊了一下。查询资料了解后,感觉自己入行没多久,感觉这个框架高深莫测,不敢涉猎。
这个周末的闲暇之余,我决定学习一下这个框架并写个登录demo。

开始了。
首先来导入框架,我使用的是Cocopods来导入框架。毕竟这个框架手动导入起来实在是太麻烦

通过stroyboard搭建了一个登录界面,并声明了三个变量:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userTF;
@property (weak, nonatomic) IBOutlet UITextField *passTF;
@property (weak, nonatomic) IBOutlet UIButton *loginBTN;
@property (nonatomic, strong) LoginService *service; //这是自己写的模拟后台
@end

导入框架后,在view controller中引入ReactiveCocoa的头文件

#import  

在viewdidload中写上代码:

RACSignal *userTFSingal = self.userTF.rac_textSignal;
RACSignal *passTFSingal = self.passTF.rac_textSignal;

[userTFSingal subscribeNext:^(id x) {
        NSLog(@"打印出来的用户名文本:%@",x);
    }];

运行之后会发现 userTF中值发生了改变就会打印一次userTF中的text;

也可以通过fliter来设定subscribeNext响应条件。

[[userTFSingal filter:^BOOL(NSString *text) {
        
        return text.length > 3;
    }] subscribeNext:^(id x) {
        NSLog(@"用户名超过三位的文本:%@",x);
    }];

map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。在上面的代码中,map以NSString为输入,取字符串的长度,返回一个NSNumber。

    [[[userTFSingal map:^id(NSString *text) {
        return @(text.length);
    }]
    filter:^BOOL(NSNumber *length) {
        return [length intValue]>5;
    }]
    subscribeNext:^(id x) {
        NSLog(@"用户名文本长度:%@",x);
    }];

上面说明了ReactiveCocoa的UITextField部分使用。下面开始实现登录逻辑吧!

设置有效文本长度


- (BOOL)isValidUsername:(NSString *)username {
    return username.length > 3;
}

- (BOOL)isValidPassword:(NSString *)password {
    return password.length > 3;
}

创建有效状态信号

    RACSignal *validUsernameSignal = [self.userTF.rac_textSignal 
map:^id(NSString *text) {
        return @([self isValidUsername:text]);
    }];
    RACSignal *validPasswordSignal = [self.passTF.rac_textSignal
     map:^id(NSString *text) {
         return @([self isValidPassword:text]);
     }];

根据文本是否有效改变文本框颜色

    [[validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor grayColor];
    }]
     subscribeNext:^(UIColor *color) {
        self.passTF.backgroundColor = color;
    }];

相对于上面这个方法更推荐使用下面这个方法:


RAC(self.userTF, backgroundColor) = [validUsernameSignal map:^id(NSNumber *userValid) {
        return [userValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

    
    RAC(self.passTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

聚合信号
combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。


RACSignal *loginSignal = [RACSignal combineLatest:@[validPasswordSignal,validUsernameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
        return @([usernameValid boolValue] && [passwordValid boolValue]);
    }];

这里是模拟后台

LoginService接口


typedef void (^RWSignInResponse)(BOOL);

@interface LoginService : NSObject

- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;

@end

实现


- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock {
    
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"];
        completeBlock(success);
    });
}

创建登录信号

- (RACSignal *)loginInSignal {
    
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [self.service signInWithUsername:self.userTF.text password:self.passTF.text complete:^(BOOL success) {
            [subscriber sendNext:@(success)];
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}

UIButton

设置按钮状态

[loginSignal subscribeNext:^(NSNumber *login) {
        self.loginBTN.enabled = [login boolValue];
    }];

UIButton部分使用

[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"登录按钮被点击了");
    }];

输出登录结果

    [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(id x) {
        NSLog(@"登录结果:%@",x);
    }];

这里你会发现打印的登录结果 不是bool类型
当点击按钮时,rac_signalForControlEvents发送了一个next事件(事件的data是UIButton)。map操作创建并返回了登录信号,这意味着后续步骤都会收到一个RACSignal。这就是你在subscribeNext:这步看到的。

上面问题的解决方法,有时候叫做信号中的信号,换句话说就是一个外部信号里面还有一个内部信号。你可以在外部信号的subscribeNext:block里订阅内部信号。不过这样嵌套太混乱啦,还好ReactiveCocoa已经解决了这个问题。

信号中的信号
解决的方法很简单,只需要把map操作改成flattenMap就可以了:

        [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^id(id value) {
            return [self loginInSignal];
        }] subscribeNext:^(NSNumber *loginIn) {
            NSLog(@"登录结果:%@",loginIn);
            BOOL success = [loginIn boolValue];
            if (success) {
                [self performSegueWithIdentifier:@"Kitten" sender:self];
            }
        }];

为了防止多次点击Button,使用doNext添加附加操作。

    [[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
        self.loginBTN.enabled = NO;
    }] flattenMap:^RACStream *(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(NSNumber *loginIn) {
        NSLog(@"登录结果:%@",loginIn);
                    BOOL success = [loginIn boolValue];
                    if (success) {
                        [self performSegueWithIdentifier:@"Kitten" sender:self];
                    }

    }];

好了 登录demo完成了。
参考文章:
最快让你上手ReactiveCocoa之基础篇
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ==>此文的初始工程
以及译文

你可能感兴趣的:(ReactiveCocoa 学习 一)