ReactiveCocoa实用案例

上一个项目用到了 RAC+MVVM 的模式,在具体使用之前,纯粹是在乱整,不过用着用着就有些门道了。这几天又具体看了下 RAC,整理出几个比较实用的小技巧,供大家参考吧。

由于读者中可能有初学者,所以本文并不结合 MVVM 一起讲解,RAC + MVVM + FMDB 的框架设计会在之后整理出来。

本文 Demo 下载地址:

https://github.com/saitjr/ReactiveCocoaUtilityDemo.git

环境信息:

Mac OS X 10.11

Xcode 7.0.1

iOS 9.0.1

ReactiveCocoa 2.4.7


正文:

案例一:登录或注册时的输入基本验证

这个案例主要用到的是 RAC 中的 map 与 combineLatest 方法,map 方法会将拿到的信号映射成新的内容,而 combineLatest 方法则可以结合多个信号量,最终得到按钮是否可用的结果。
1. 判断用户名是否合法

// 设置用户名是否合法的信号
// map用于改变信号返回的值,在信号中判断后,返回bool值
RACSignal *usernameSignal = [self.usernameTextField.rac_textSignal map:^id(NSString *usernameText) {
        
    NSUInteger length = usernameText.length;
    
    if (length >= 1 && length <= 16) {
        return @(YES);
    }
    return @(NO);
}];

2. 判断密码是否合法(与用户名判断类似,不详讲)

3. 判断确认密码是否合法

// 设置确认密码合法的信号
// 因为确认密码的文本要和密码的文本有关联,无论确认密码修改还是密码修改,都需要及时判断,所以需要绑定两个信号量
RACSignal *comfirmSignal = [RACSignal combineLatest:@[self.passwordTextField.rac_textSignal, self.comfirmTextField.rac_textSignal] reduce:^id(NSString *passwordText, NSString *comfirmText){
    
    NSUInteger length = comfirmText.length;
    
    if (length >= 1 && [comfirmText isEqualToString:passwordText]) {
        return @(YES);
    }
    return @(NO);
}];

4. 通过前三个信号量,判断得到注册按钮是否可用

// 绑定用户名、密码、确认密码判断结果的三个信号量,如果三个都为真,则按钮可用
RAC(self.registerButton, enabled) = [RACSignal combineLatest:@[usernameSignal, passwordSignal, comfirmSignal] reduce:^(NSNumber *isUsernameCorrect, NSNumber *isPasswordCorrect, NSNumber *isComfirmCorrect){
    
    return @(isUsernameCorrect.boolValue && isPasswordCorrect.boolValue && isComfirmCorrect.boolValue);
}];

案例二:搜索

在用户注册的过程中,很有可能就会有重名的情况,如果在提交时再告诉用户名字被占用,用户体验大大的不好。所以很多 App 或网站都会采用边输入边验证的方式,那么这就需要向后台频繁的发起网络请求,来看看 RAC 中怎么处理。

整体思路如下:

1. 在搜索框文本变化时,先判断输入内容是否合法(至少过滤掉空文本)

@weakify(self);
[self.searchTextField.rac_textSignal
// 先将不合法的搜索词过滤掉(返回的bool值决定了signal是否继续向下传递)
filter:^BOOL(NSString *searchKeyword) {
        
    return @(searchKeyword.length);
}]

2. 如果合法,也没必要频繁的发起请求。所以我设定 0.5s 发起一次

@weakify(self);
[[self.searchTextField.rac_textSignal
// 先将不合法的搜索词过滤掉(返回的bool值决定了signal是否继续向下传递)
filter:^BOOL(NSString *searchKeyword) {
        
    return @(searchKeyword.length);
}]
// 因为没必要每次一输入便进行网络请求,所以0.5s之后,才进行搜索。(throttle是在规定时间后,信号继续向下传递)
throttle:0.5]

3. 因为在我的设计中,网络请求成功以后,会返回一个信号,所以使用 flattenMap 将以前搜索框的信号直接映射成网络请求返回的信号。

@weakify(self);
[[[self.searchTextField.rac_textSignal
// 先将不合法的搜索词过滤掉(返回的bool值决定了signal是否继续向下传递)
filter:^BOOL(NSString *searchKeyword) {
        
    return @(searchKeyword.length);
}]
// 因为没必要每次一输入便进行网络请求,所以0.5s之后,才进行搜索。(throttle是在规定时间后,信号继续向下传递)
throttle:0.5]
// 网络请求将会返回signal,所以直接使用flattenMap来映射,而不必用map
flattenMap:^RACStream *(NSString *searchKeyword) {
        
    @strongify(self);
    // 发起网络请求
    return [self searchWithKeyword:searchKeyword];
}]

4. 结果返回后,有可能需要更新 UI,如果不在主线程,先回到主线程

@weakify(self);
[[[[self.searchTextField.rac_textSignal
// 先将不合法的搜索词过滤掉(返回的bool值决定了signal是否继续向下传递)
filter:^BOOL(NSString *searchKeyword) {
        
    return @(searchKeyword.length);
}]
// 因为没必要每次一输入便进行网络请求,所以0.5s之后,才进行搜索。(throttle是在规定时间后,信号继续向下传递)
throttle:0.5]
// 网络请求将会返回signal,所以直接使用flattenMap来映射,而不必用map
flattenMap:^RACStream *(NSString *searchKeyword) {
        
    @strongify(self);
    // 发起网络请求
    return [self searchWithKeyword:searchKeyword];
}]
// 回到主线程,因为在signal订阅中可能更新界面
deliverOnMainThread]

5. 这时,在订阅,则是订阅的网络请求返回的信号了,而 block 的参数,则是网络请求返回的结果。

@weakify(self);
[[[[[self.searchTextField.rac_textSignal
// 先将不合法的搜索词过滤掉(返回的bool值决定了signal是否继续向下传递)
filter:^BOOL(NSString *searchKeyword) {
    
    return @(searchKeyword.length);
}]
// 因为没必要每次一输入便进行网络请求,所以0.5s之后,才进行搜索。(throttle是在规定时间后,信号继续向下传递)
throttle:0.5]
// 网络请求将会返回signal,所以直接使用flattenMap来映射,而不必用map
flattenMap:^RACStream *(NSString *searchKeyword) {
    
    @strongify(self);
    // 发起网络请求
    return [self searchWithKeyword:searchKeyword];
}]
// 回到主线程,因为在signal订阅中可能更新界面
deliverOnMainThread]
// 订阅网络请求返回的信号
subscribeNext:^(id x) {
    
    NSLog(@"%@", x);
}];

案例三:解决 block 嵌套问题

看 RAC 的时候,就说 RAC 能解决 block 嵌套的问题,但是很多资料都没有具体的实现,所以我在这里讲解下。

具体的例子是有序的执行网络请求。比如要等一个请求回来以后,才发起下一个。(虽然这个需求能用其他的办法解决,但是这里讲解 RAC 的解决方案。AFNetworking 的方法会在之后的博客中介绍到。)

这个案例用到的是 RAC 中的 concat 拼接方法,这个方法会在第一个信号 sendNext 之后,才会激活第二个信号,从而达到有序触发的目的。

// 假设发起两个请求
// request1与request2是自定义方法,会返回两个信号
RACSignal *signal1 = [self request1];
RACSignal *signal2 = [self request2];

[[signal1 concat:signal2] subscribeNext:^(id x) {
    
    NSLog(@"%@", x);
}];

- (RACSignal *)request1 {
    
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            [subscriber sendNext:@"请求1完成"];
            [subscriber sendCompleted];
        });
        
        return nil;
    }];
}

案例四:同时发起多个请求,直到全部完成

发起多个请求,直到所有请求都完成后,得到最终结果。

这个需求可以用之前的 combineLatest 方法,也可以用 merge 方法。这两个方法都是要在全部信号都 sendNext 以后才会触发最终结果的。

[[signal1 merge:signal2] subscribeNext:^(id x) {
    
    NSLog(@"%@", x);
}];

最后

RAC 的设计和用法都很值得学习,这是接下来的研究任务。现在响应式编程,链式编程,函数式编程正火,Github 上也有众多代表,大家快去加餐吧。

再本文 Demo 贴一遍地址:

https://github.com/saitjr/ReactiveCocoaUtilityDemo.git

参考文章

http://www.jianshu.com/p/e10e5ca413b7/comments/770468#comment-770468

http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2

 

《ReactiveCocoa实用案例》有3个想法

    1. 这个需求可以用之前的combineLatest方法,也可以用merge方法。这两个方法都是要在全部信号都sendNext以后才会触发最终结果

      这句话错了,combinelatest方法是所有的信号都发送过sendnext,才会触发最终效果。

      而merge方法只要其中任何一个信号发出了sendnext,都会触发最终效果

发表评论

电子邮件地址不会被公开。