iOS

MVVM终结者(三)

2016-03-03  本文已影响178人  洪小倲

signal的Operations

signal的Operations或者称它是signal的运算与操作,对signal的操作其实是定义在signal的父类RACStream中的,RACStream是一个抽象类,描述了值的流动,不过我至今没有直接用过RACStream,人家都叫抽象类了,非常抽象,还去理解它干嘛。

就跟数字的运算一样,运算过后会生成一个新的数字,signal的运算过后会生成一个新的信号,下面就列举一些signal的运算介绍一下用法

[signal filter:^(NSString *newName) { 
    return [newName hasPrefix:@"h"]; 
}];// 返回一个新的signal,这个signal中的值流动只有h开头的字符串
[signal map:^(NSString *value) { 
    return [NSString stringWithFormat:@"abner%@",value]; 
}];// 返回一个新的signal,这个signal中的值流动为原来字符串值的前面拼接上abner
[signal ignore:nil];// 返回一个新的忽略掉原来nil值的信号
 [[RACSignal merge:@[self.nameField.rac_textSignal,
                      RACObserve(self.nameField, text)]] 
    subscribeNext:^(NSString* text){ 
      // do something 
   }];

不论上面哪个信号有值流动,新的信号都会有值流动

RAC(self, createEnabled) = [RACSignal  combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]  
      reduce:^(NSString *password, NSString *passwordConfirm) { 
      return @([passwordConfirm isEqualToString:password]); 
}];

可能有点超纲,我稍微解释一下,等号后面的信号当password和passwordConfirmation的值一样的时候才会有@YES的值流动,等号左边宏RAC的作用是把createEnabled这个属性的值和右边信号中的值流动单向的绑定在一起,右边信号中一旦有值流动会自动赋值给createEnabled属性,相信上面代码的应用场景你已经知晓了现在,把createEnabled改成button的enable属性是不是就能实现以前挺多代码才能实现的逻辑。

[[[signal  
flattenMap:^(User *user) {  
    //上面的信号出正确的值流动的时候会执行这个block 
    return [client loadCachedMessagesForUser:user]; // 这里返回一个信号 }]  
subscribeNext:^(NSArray *newMessages) {  
    //这里的值流动是第二个信号中的值流动 
    NSLog(@"New messages: %@", newMessages);  
} completed:^{  
    NSLog(@"Fetched all messages.");  
}]; 

上面这段代码可以理解为:这个运算产生的新的信号中的值流动是block中return回去的那些信号的值流动,而block中的信号的创建往往要依赖于原信号中吐出来的值,也就是这里其实是有个先后顺序的--原信号产生一个值流动,然后block中根据这个值流动创建一个信号并把这个信号中的值流动添加到运算过后最终的信号里面。原信号再次产生值流动会重复上面的逻辑。假设如果原信号只会吐一次数据就结束了(比如说网络请求signal),那么这个运算的就正好可以处理连接两个有顺序要求的信号。

[[self signInSignal] takeUntil:self.cancelSignals]; // 登入信号会在取消信号流出@YES的时候取消掉。

cancat flatmap then 区别

这三个运算的功能貌似很接近,所以有必要单独拿出来区分一下,首先上面我也提到的cancat:then:基本上是一个东西,只是then:把原signal的值流动都ignore掉从而不对后面的signal产生任何的影响,也就是then只是单纯的表示这两个signal的先后顺序。

终于把signal的运算也给写完了,貌似看到这里就可以开始用MVVM+RAC来重构你手中的代码了,接来来贴一些具体的应用场景来理论与实践相结合一下。

map + switchToLatest

如果把这两个结合起来就有意思了,想象这么个场景,当用户在搜索框输入文字时,需要通过网络请求返回相应的hints,每当文字有变动时,需要取消上一次的请求,就可以使用这个配搭。这里简单演示一下

NSArray *pins = @[@172230988, @172230947, @172230899, @172230777, @172230707];
__block NSInteger index = 0;
RACSignal *signal = [[[[RACSignal interval:0.1 
                    onScheduler:[RACScheduler scheduler]] 
                    take:pins.count] 
                    map:^id(id value) { 
                      return [[[HBAPIManager sharedManager] fetchPinWithPinID:[pins[index++] intValue]] 
                              doNext:^(id x) { 
                                  NSLog(@"这里只会执行一次"); 
                              }]; 
                      }] switchToLatest];

[signal subscribeNext:^(HBPin *pin) { 
      NSLog(@"pinID:%d", pin.pinID);} completed:^{ 
      NSLog(@"completed");
}];
// output// 2014-06-05 17:40:49.851 这里只会执行一次
// 2014-06-05 17:40:49.851 pinID:172230707
// 2014-06-05 17:40:49.851 completed

takeUntil的使用来解决cell被复用后产生的问题

[[[cell.detailButton rac_signalForControlEvents:UIControlEventTouchUpInside] 
                      takeUntil:cell.rac_prepareForReuseSignal] 
                      subscribeNext:^(id x) { 
                          // generate and push ViewController
                      }];

cell一旦被复用,那么这个监听就接触了。如果不加takeUntil:cell.rac_prepareForReuseSignal,那么每次Cell被重用时,该button都会被addTarget:selector。

检查本地缓存,如果失效则去请求网络数据并缓存到本地

//创建一个信号,如果缓存还有效则值流动为缓存,如果缓存失效了则流出去一个缓存失效的错误。
- (RACSignal *)loadData { 
    return [[RACSignal 
              createSignal:^(id<RACSubscriber> subscriber) { 
                if (self.cacheValid) { // 缓存有效
                  [subscriber sendNext:self.cachedData]; 
                  [subscriber sendCompleted]; 
                } else { // 缓存无效
                  [subscriber sendError:self.staleCacheError]; 
                } 
               }
              ] subscribeOn:[RACScheduler scheduler]
            ];
}

- (void)update { 
    [[[[self loadData] 
                catch:^(NSError *error) { // 如果本地缓存失效的话
                    return [[self updateCachedData] // 重新获取数据
                                  doNext:^(id data) { 
                                      [self cacheData:data]; // 缓存数据
                                      [self update];//再次调用自己来更新UI 
                                  }
                           ]; 
     }] deliverOn:RACScheduler.mainThreadScheduler] 
    subscribeNext:^(id data) { 
          // 获取有效的数据,更新UI
    }]; 
}

动态检查用户名是否可用

Paste_Image.png

可以看到这里也使用了map + switchToLatest模式,这样就可以自动取消上一次的网络请求。startWith是设置一个signal的初始值流动。

token过期后自动获取新的

下一篇开始用MVVM搭建IOS程序的过程中一些连接的纽带,我看见网上也少有介绍这些的文章,导致真正用MVVM搭建项目的时候发现有些地方衔接不上,比如说界面之间的跳转是属于逻辑层,只有在逻辑的处理过程中才知道什么时候该跳转以及跳转到什么地方去,而在逻辑层VM中是不能拥有VIEW的句柄的,那VM到底是如何跳转的呢?这个问题等到下一篇文章再书。
未完待续

上一篇 下一篇

猜你喜欢

热点阅读