MVVM终结者(二)

RAC就像是MVVM的翅膀一样,有了RAC才使得MVVM更加的得心应手。RAC能够使逻辑更加集中,更好处理一些复杂的逻辑。

刚开始接触RAC肯定是一头的雾水,或许你已经上网看过了一些RAC的简单教程,但你依然不懂RACSignal、RACCommand这些到底是什么东西,你可能知道了signal水管的比喻或者是排插的比喻,这些比喻确实是挺确切的,但是一开始学习的时候这些比喻着实还是不太好理解。

RAC一个最核心的功能就是它提供了统一的方法来处理等待值流动等待值流动等待值流动就是等待指定事情的发生并且传出相应的值。在IOS开发中等待值流动的表现形式其实的最多的就是异步行为、代理方法,回调blocks,target-action机制,通知和KVO。这些东西的本质其实都是等待值流动,他们都最终告诉你事情发生了然后你处理接下来的逻辑。而在RAC中这些等待值流动统一成了RACSignal,所有的这些等待值流动都可以转成一个RACSignal对象,既然统一了这些值流动的出口,你就不必再零散的写代码,你可以提前并且集中的处理这些值流动以后的逻辑,并且signal具备可操作、可变换、可链接的特性能够更好的统一处理逻辑。

RACSignal其实就是一个能通知指定事情的到来并传递数据给订阅者的一个东东而且有一类RACSubject的signal自己本身就可以充当一个订阅者

接下来我们逐个地把IOS开发中常用的值流动转化为RACSignal

先来小小的窥视一下RACSignal大概是如何实现的,上面的提到了RACSignal能够传递数据给订阅者,代码上实现起来就是简单的消息发送传参数的过程也就是调用订阅者的方法,很显然一个signal的实例中就必须包含着它订阅者的实例才能在流动值到来的时候调用订阅者的方法来为这个订阅者传递数据,看一段signal创建的代码

RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) {
          [subscriber sendNext:@"abner"];
          [subscriber sendCompleted];
          return nil;
}];

创建signal的时候传入一个block,block中要求返回一个 RACDisposable的对象(这个东西后面再说),而block中传给你用的参数是一个实现了RACSubscriber协议的对象,这个协议也就是订阅者协议,也就是说这里传给你使用的参数是一个这个signal的订阅者,至于订阅者为什么要用协议的实现方式是因为在这之中各种各样的实例都可以是订阅者(比如我上面提到的那类signal本身就是一个实现了这个协议的订阅者)。回到这个block本身,这个block里面就是当一个订阅者订阅了这个信号后这个信号自身会做的事情,主要就是把数据传递给这个signal的订阅者,我们把要传递的数据分为三大类并通过不同的方法传参数给订阅者,分别是:

  • 普通数据————通过调用订阅者的sendNext:方法来传递普通数据。
  • 错误数据————通过调用订阅者的sendError:方法来传递错误数据。
  • 完成标志数据————通过调用订阅者的sendComplete方法来传递这次的订阅处理已经完成,解除订阅,不会继续传递数据给这个订阅者,会自动调用订阅的释放以及后续的清理工作。

以上三种类型的数据传递的理解先放一放,我们先来看一下订阅者对一个signal的订阅

[信号 subscriber:订阅者];//调用信号的subscriber方法并传给一个订阅者对象

上面这句伪代码就是订阅者订阅信号,每次有订阅者订阅signal都会执行那个初始化signal时候传入的block从而把signal的值流动传递给订阅者,当时在实际的开发过程中你并不关心订阅者是谁,你关心的只是收到这个值流动以后应该做些什么,所以忘掉订阅者这个东西吧,你更多的时候用到的是下面的代码:

[signal subscribeNext:^(id x) {//普通数据的值出口
      NSLog(输出值流动x);
}];

[signal subscribeError:^(NSError *error) {//Error数据的值出口
      NSLog(输出值流动x);
}];

[signal subscribeCompleted:^() {//完成标识数据的值出口
    
}];

上面三个写法的实现其实是用闯入的block创建一个监听者然后通过方法subscriber:监听signal。至于为什么要把数据分成这三类并且分开出口,我想大多数时候数据的正确的逻辑和数据错误的逻辑都是不一样的,就比如一个网络的请求,正确的数据就会隐藏HUD,刷新UI,而错误的数据则会显示错误信息。既然都谈到了网络请求这个开发当中最大块的异步行为,那接下来就先把网络请求通过上述的signal创建方法转换成signal的形态。直接先上代码

网络请求转换成signal

下面的网络封装以AFNetwork3.0+作为基础,封装为AFHTTPSessionManager的一个category,所以代码中所有的self都可以替换为一个AFHTTPSessionManager的实例对象来理解,相信熟悉AF的童鞋配合注解都能够理解

- (RACSignal *)rac_requestPath:(NSString *)path parameters:(id)parameters method:(NSString *)method {
       return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
          NSURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
          NSURLSessionDataTask *task = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
               if (error) {
                  [subscriber sendError:error];//网络异步返回的错误通过这句发送给订阅者
               } else {
                  [subscriber sendNext:RACTuplePack(responseObject, response)];//网络请求成功的正常数据通过这句发送给订阅者
                  [subscriber sendCompleted];//这一次网络请求结束,不再发送数据,所以发送完成标识
               }
         }];

          [task resume];
  
          return [RACDisposable disposableWithBlock:^{
             [task cancel];//订阅完成或者取消,释放资源
          }];
 }];
}

上面是一段特地简化的代码,在实际开发当中网络请求的正确错误的逻辑走向并不能单单依据请求error来判断,逻辑错误也很有可能走错误的逻辑。可以看出转换过程并不复杂,就是把异步中的block的数据输入输出到订阅者中就可以完成简单的转换了,到这里我们就可以把网络请求全部换成signal的形态了。

KVO转换成signal

你是不是也跟我一样早已经厌倦了传统的KVO的复杂冗长分离的写法,忘记它吧,把KVO转换成signal只需要短短的一行代码,一切能够KVO的属性都能够通过下面的一个RAC封装的宏来转换成相应的signal。

RACObserve(监听目标 , 目标的属性) //这个宏的返回值是一个signal
通知转换成signal

RAC为NSNotificationCenter类做了便捷的把通知转换成signal的扩展

[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"abner" object:nil]; //返回值是一个sinal,把监听名字为abner的通知转换为了信号,一旦发送了名为abner的通知,这个信号就会有值流动。

整容前你的接收通知的代码可能是这样:

-(void)observer {  
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(callBack1:) name:@"abner" object:nil];
    //结束完后需要删除名字为abner的observer  
//    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"abner" object:nil];       
}  
  
-(void)callBack1:(NSNotification*)notification{  
    NSString *nameString = [notification name];  
    NSString *objectString = [notification object];  
    NSLog(@"name = %@,object = %@",nameString,objectString);  
} 

这里的逻辑分离程度令人发指,显示注册监听,然后又在外面实现通知到来逻辑,还需要在合适的地方删除这个监听,也就是这一套东西的代码分离在了三个不同的地方。长期以往必然导致精神分裂。而整容以后你的接收通知的代码长这样:

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"abner" object:nil] subscribeNext:^(id x) {
        do someing 少年 //动手把你手头上的通知都改成这样吧
}];
target-action机制转换成signal

原生target-action机制的写法也是比较分离的,RAC为UIControl添加的category可以很方便的把target-action转换成signal:

    [testButton rac_signalForControlEvents:UIControlEventTouchUpInside]; //返回一个signal,可以通过subscribeNext:来添加入订阅者并处理事件发生后的逻辑。

UIControlEventTouchUpInside事件如此,相信其他事件你也能够无师自通了。

代理机制转换成signal

直接上代码可能你跟容易理解:

[self rac_signalForSelector:@selector(代理方法) fromProtocol:代理协议]; //返回一个signal,前面的self是任意代理指向的对象

其实上面那句代码本质上是当self这个对象触发某个指定方法时候这个signal会产生值流动,它有一个很像的方法如下:

[self rac_signalForSelector:@selector(某个方法)]; //返回一个signal,前面的self是任意监听的对象

貌似到这里基本上所有的东西都变成信号了,利用上面的方法你现在可以把一些常用的东西都转化成信号了:

  • UITextField、UITextView的text变化信号---本质上是监听指定方法的调用(UIControlEventAllEditingEvents)和代理方法转信号
  • UISearchBar的text变化信号和搜索按下的信号---本质上是代理方法转信号
  • UIActionSheet的按钮点击选择的信号---本质上也是代理方法转信号

等等,这些你想的到的想不到的,很庆幸的是大部分的常用的上面提到的信号RAC都已经帮你封装好了,使用的是UIKit的category的形式,我就拿最常用的UITextField的用法说一说,其他的用法基本一样

[textField.rac_textSignal subscribeNext:^(id x){
    // 一旦用户通过键盘输入到textField中,这里的block就会触发,并且把text中的值传递下来
}]

至此、貌似你真正的可以把基本上所有的IOS开发中的值流动都转换成signal,这时候你手上拽着一大堆的signal除了能把每个signal都subscribeNext:然后处理各自signal的逻辑以外貌似别无他用了,signal与signal之间还是各自独立的,并无关联。如果仅仅是这样那RAC也不会被我奉为神器,这个系列的文章更不会叫它终结者了。

回头看看

[textField.rac_textSignal subscribeNext:^(id x){
    // 一旦用户通过键盘输入到textField中,这里的block就会触发,并且把text中的值传递下来
}]

这段代码中的注解,“一旦用户通过键盘输入“,我写注解都是很严谨的为什么这里要强调键盘输入,这也是我一开始接触RAC的时候掉过的一个坑,除了键盘输入还有一中方法能改变UITextField中显示的内容,就是通过Copy,Paste进来的值,你不妨去盯一下,这个时候其实UIControlEventAllEditingEvents这个事件是不触发的,通过Copy,Paste进来的值会直接改变UITextField的text属性的值,也就是说这种情况下只能用[RACObserve(textField,text)]的方式来转换成信号,但是大多数情况下这两种signal的值流动的处理逻辑是一样的,如果有两个signal的话那处理逻辑就要写两遍或者调两次同一处理逻辑,这样就导致逻辑分散,所以我们就想能不能把这两个signal先揉成一个signal,然后再subscribe:这个signal统一处理逻辑,你能这么想就说明你能理解了signal到底是个什么样的东西了,接下来我们就说说signal与signal的Operations,或许把它翻译理解为signal的运算。见终结者(三)

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

推荐阅读更多精彩内容