MVVM终结者(三)

signal的Operations

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

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

  • filter:(过滤)--返回一个过滤过原本值流动内容的signal
[signal filter:^(NSString *newName) { 
    return [newName hasPrefix:@"h"]; 
}];// 返回一个新的signal,这个signal中的值流动只有h开头的字符串
  • map:(映射)--返回一个值流动内容的映射为内容的signal
[signal map:^(NSString *value) { 
    return [NSString stringWithFormat:@"abner%@",value]; 
}];// 返回一个新的signal,这个signal中的值流动为原来字符串值的前面拼接上abner
  • ignore:(忽略)--返回一个新的信号,这个信号传递的值流动是原来值流动忽略掉指定值的值流动
[signal ignore:nil];// 返回一个新的忽略掉原来nil值的信号
  • merge:(合并)--把多个信号中传递的值流动合并在一个信号中流动,会不会有人在问“那这些值出来的顺序是什么样的?”,我在刚开始接触RAC的时候也掉过这个坑,为什么叫值流动而不叫值,值在时间轴下的走向才叫值流动,这些待合并的信号依赖的是同一个时间轴,先流出来的值在新的信号中也是先流出来,想象成几个水管,把这几个水管接到了一个接口出来,水流的速度都是一样的,先开水龙头的水管的水肯定是先出来的。现在回到前面一篇文章提出的UITextField的两个信号“揉”在一起的问题,结合下面的代码例子理解理解
 [[RACSignal merge:@[self.nameField.rac_textSignal,
                      RACObserve(self.nameField, text)]] 
    subscribeNext:^(NSString* text){ 
      // do something 
   }];

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

  • combineLatest:(结合)--结合多个信号,当其中一个信号有值流动时,取所有信号最新的值流动作为新的信号的值流动,注意这里会取所有信号的最新值,也就是说所有的信号必须都得有过值流动(包括空值的流动),如果其中一个信号从未又过值流动则新的信号也不会有值流动,所有的信号中的值流动都是有序而且不能多个值绑在一起流动,而这里的多个值到底是如何绑在一起流动的呢?彻底理解这个先想想swift中的函数是如何返回多个值的,其实原理跟这个一样,先把多个返回值封装成一个返回值(元组)然后返回,在RAC中叫RACTuple,combineLatest:产生的新信号中的值流动就是RACTuple。

  • reduceEach: --针对值流动时RACTuple的信号的,新信号中值流动是根据RACTuple中的数据返回的一个新值,其实这个操作就是个map操作,那为什么要把这么不重要的东西提出来说,因为combineLatest:reduceEach:经常组合起来使用,先combineLatest:然后reduceEach:就可以把多个信号中的最新值流动变成一个值流动的新信号,当然这种官配RAC肯定也给我们封装好了

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属性是不是就能实现以前挺多代码才能实现的逻辑。

  • flatten --只能作用在signal of signals中,signal of signals就是一种值流动是signal的signal。产生的新信号是merge了里面所有的信号。

  • flatmap: --flatmap:传入一个block,这个block需要返回一个signal,每次原来的信号有值流动就会重新执行这个block,所以这里存在很多个return回去的signal,这个运算产生的新的信号中的值流动是meger这些signal以后的值流动。也可以解释为先把普通的signal通过map:变成signal of signals然后再flatten,虽然可以这么解释当时RAC实现起来是先有flatmap:然后flatten其实是封装调用了flatmap的。

[[[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),那么这个运算的就正好可以处理连接两个有顺序要求的信号。

  • concat:(连接) --真正的连接在这个地方,产生一个新的信号,这个新信号只有等原信号发出了complete标示了以后然后才执行第二个信号(实际上是执行信号被订阅时候的发送数据给订阅者的block),第二个信号中的值流动会出现在新信号中。很简单的就不贴例子了。

  • then: -- then这个其实很好理解,两个有先后顺序的信号的拼接组合,其实then是封装了一下的concat,只是把第一个信号的值流动都给ignore掉,不对第二个信号产生任何的影响。

  • distinctUntilChanged--新信号的值流动中相邻两个值是不一样,原信号中如果相邻两个值是一样的则第二个值流动不会加入到新信号值流动中去

  • doNext: --传入一个block,这个block会在这个新信号被subscribeNext:之前被执行,不要把这个运算理解成接下来要做什么,之前我看这个英文掉过一次坑。

  • deliverOn: --参数为RACScheduler类的对象scheduler,这个方法会返回一个新Signal,针对这个signal的的所有事件都会传递给scheduler参数所表示的线程上执行,而以前管道上的副作用还会在以前的线程上。这个方法主要是切换线程。

  • subscribeOn: --拥有deliverOn:的功能,并且这个运算还会把原信号之前的事件传递到响应的线程中去执行。

  • throttle: --它接收一个时间间隔interval作为参数,如果Signal发出的next事件之后interval时间内不再发出next事件,那么它返回的Signal会将这个next事件发出。也就是说,这个方法会将发送比较频繁的next事件舍弃,只保留一段“静默”时间之前的那个next事件,这个方法常用于处理输入框等信号(用户打字很快),因为它只保留用户最后输入的文字并返回一个新的Signal,将最后的文字作为next事件参数发出。

  • switchToLatest: -- 作用是在signal of signals中,产生的新信号的值流动时原信号中流动的最新的一个signal的值流动,通俗的讲就是切换到里面最新的一个的意思。

  • takeUntil: --返回的新信号跟原信号的值流动是一样的,不过当传入的信号sendNext时,新的signal就sendCompleted,也就是就不继续发送数据了。

[[self signInSignal] takeUntil:self.cancelSignals]; // 登入信号会在取消信号流出@YES的时候取消掉。
  • and、or、not NSNumber中Bool的与、或、非操作,将Signal发出的事件内容转化。

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到底是如何跳转的呢?这个问题等到下一篇文章再书。
未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • RAC就像是MVVM的翅膀一样,有了RAC才使得MVVM更加的得心应手。RAC能够使逻辑更加集中,更好处理一些复杂...
    洪小倲阅读 766评论 2 7
  • RAC使用测试Demo下载:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees阅读 6,341评论 3 10
  • 1.ReactiveCocoa常见操作方法介绍。 1.1 ReactiveCocoa操作须知 所有的信号(RACS...
    萌芽的冬天阅读 1,010评论 0 5
  • 标签: iOS RAC 概述 ReactiveCocoa是一个函数响应式编程框架,它能让我们脱离Cocoa AP...
    GodyZ阅读 7,492评论 16 97
  • 前言由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目...
    Karos_凯阅读 1,711评论 0 6