一个MVVM的学习记录和感悟,我自己也在慢慢学习中,这个DEMO会慢慢完善并且会把学习中需要注意的东西记录下来。感谢关注阅读

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

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发出的事件内容转化。

一、前言

一、前言

在刚接触IOS开发的时候,各种教科书上面都写着IOS是原生的MVC结构,先看一个典型的
iOS 是如何构建的:

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

cancat flatmap then 区别

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

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

      前些天需要完成一个任务,该任务属于公司的一些核心代码,为了避免不必要的麻烦,任务要求不能使用第三方的MVVM框架,必须用原生的。

      前些天需要完成一个任务,该任务属于公司的一些核心代码,为了避免不必要的麻烦,任务要求不能使用第三方的MVVM框架,必须用原生的。

图片 1Typical
Model-View-Controller setup

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

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

      
平时习惯了Dev与MVVMLight,遇上原生的说实话还真不会,于是写下来当做备忘录。 
(界面老司机可以直接不看了)

      
平时习惯了Dev与MVVMLight,遇上原生的说实话还真不会,于是写下来当做备忘录。 
(界面老司机可以直接不看了)

依据上面的MVC构建IOS程序有着得天独厚的优势。因为

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

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。

 

 

  • 可以很方便的写出继承于NSObject类的Model
  • 并且有针对View设计的UIView类
  • 以及针对C设计的UIViewController类
接下来我们逐个地把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的形态。直接先上代码

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

//创建一个信号,如果缓存还有效则值流动为缓存,如果缓存失效了则流出去一个缓存失效的错误。
- (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
    }]; 
}

二、代码范例

二、代码范例

但是仔细想一想,虽然IOS开发kit设计了三个看上去分离的类,但其实UIView和UIViewController往往是不分离的,UIViewController中原生就带着一个UIView的成员变量(也就是IOS应用程序中的每一个界面),所以说UIViewController其实更像是UIView的管理器,或者说UIViewController是封装了一层的UIView,所有的数据处理和逻辑全部就写在了UIViewController这个类中,就导致了逻辑其实已经和View绑在了一起,其实所谓的IOS中MVC其实是M两个部分构成,由于所有的view和业务逻辑都在这个类中,这样就导致了在UIViewController这个类不伦不类,代码混乱,在这个类中代码的量是非常多的,而且理想中的MVC中V和M这两个部分都是可以高度复用的,然而因为这里V和C粘在了一起就导致了这里的V也很难被复用(关于复用其实还设计到胖model和瘦model,这里我不做介绍,想了解清楚的自行google),基于

网络请求转换成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的形态了。

动态检查用户名是否可用

图片 2

Paste_Image.png

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

      View的部分就直接略过了,看Model部分:

      View的部分就直接略过了,看Model部分:

  • 代码臃肿,逻辑混乱、分离
  • 难复用
KVO转换成signal

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

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

token过期后自动获取新的

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

public class Model : INotifyPropertyChanged
{
        public string Isbn { get; set; }

        public string Description { get; set; }


        //需要改变的属性值
        private string _AS;

        public string AS
        {
            get
            {
                return _AS;
            }
            set
            {
                _AS = value;
                NotifyPropertyChanged("AS");
            }
        }



        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
}
public class Model : INotifyPropertyChanged
{
        public string Isbn { get; set; }

        public string Description { get; set; }


        //需要改变的属性值
        private string _AS;

        public string AS
        {
            get
            {
                return _AS;
            }
            set
            {
                _AS = value;
                NotifyPropertyChanged("AS");
            }
        }



        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
}

这两个就足以让代码强迫症患者无法忍受的缺点,不惜一切代价去改变。想让MVC真正的MVC起来、要做的就只是从VC中把逻辑给分离了出来,让只做一个纯粹的View,分离出去一个继承NSObject的逻辑类对象,M还是那个M。我觉的这才是真正的MVC也是IOS原本就应该有的样子。人们还给分离出去的逻辑类对象另外一个名字叫viewModel,于是改进后的MVC就叫做M-V-VM。

通知转换成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 少年 //动手把你手头上的通知都改成这样吧
}];

 

 

在平常开发的过程中,VM需要告诉View该怎么展现自己,所以view初始化的时候一般要传入一个vm,也就是先有VM再有View,在view中持有一个VM的句柄,但是VM是不能拥有view的,所以这里的持有关系是单向的。view需要向用户动态展现在vm中的逻辑处理后不断变化的数据,同样用户所有对于view的操作过后的数据值流变化都要写入vm,也就是说view现在变得特别的单纯,单纯的只认识VM一个人,view所有的行为都是在跟VM进行交互。但是view要显示的数据和数据的处理逻辑全部都是在VM中,而上面说到VM中是没有拥有view的,那在VM中逻辑处理完了以后,view自己是怎么实现更新自己的呢?这就要用到KVO来监听VM的变化了,幸运的是ReactiveCocoa的出现,简化封装了大量这些操作、也彻底把MVVM搬上了IOS开发的大舞台。接下来的MVVM终结者后续篇是大篇幅的RAC教程

target-action机制转换成signal

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

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

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

      ViewModel部分:

      ViewModel部分:

代理机制转换成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的运算。见终结者(三)

public class VM : INotifyPropertyChanged
{
        private string currentIsbn = string.Empty;
        public string CurrentIsbn
        {
            get
            {
                return currentIsbn;
            }
            set
            {
                currentIsbn = value;
                NotifyPropertyChanged("CurrentIsbn");
            }
        }

        public ObservableCollection<Model> Isbns { get; set; }


        private ICommand _confirmCmd;
        public ICommand ConfirmCmd
        {
            get { return _confirmCmd ?? (_confirmCmd = new ConfirmFunction(this)); }
            set
            {
                _confirmCmd = value;
            }
        }


        public VM()
        {
             Isbns = new ObservableCollection<Model>();
        }


        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
 }


 public class ConfirmFunction : ICommand
 {
        private VM vm;

        public ConfirmFunction(VM _vm)
        {
            this.vm = _vm;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
           // do your job
        }
  }
public class VM : INotifyPropertyChanged
{
        private string currentIsbn = string.Empty;
        public string CurrentIsbn
        {
            get
            {
                return currentIsbn;
            }
            set
            {
                currentIsbn = value;
                NotifyPropertyChanged("CurrentIsbn");
            }
        }

        public ObservableCollection<Model> Isbns { get; set; }


        private ICommand _confirmCmd;
        public ICommand ConfirmCmd
        {
            get { return _confirmCmd ?? (_confirmCmd = new ConfirmFunction(this)); }
            set
            {
                _confirmCmd = value;
            }
        }


        public VM()
        {
             Isbns = new ObservableCollection<Model>();
        }


        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
 }


 public class ConfirmFunction : ICommand
 {
        private VM vm;

        public ConfirmFunction(VM _vm)
        {
            this.vm = _vm;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
           // do your job
        }
  }

 

 

小结:

小结:

1.
Model与ViewModel继承INotifyPropertyChanged

1.
Model与ViewModel继承INotifyPropertyChanged

2.
与View有交互的绑定字段需要如下格式:

2.
与View有交互的绑定字段需要如下格式:

    private string
_a;

    private string
_a;

    public string a
    {
            get
            {
                return _a;
            }
            set
            {
                _a = value;
                NotifyPropertyChanged(“a”);
            }
     }

    public string a
    {
            get
            {
                return _a;
            }
            set
            {
                _a = value;
                NotifyPropertyChanged(“a”);
            }
     }

3. 所有命令都为ICommand类型,然后再依照上面的格式通过Delegate完成

3. 所有命令都为ICommand类型,然后再依照上面的格式通过Delegate完成

4.
写Command类的时候VS会提示你需要哪些方法的,哈哈哈

4.
写Command类的时候VS会提示你需要哪些方法的,哈哈哈

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图