• 最快让你上手ReactiveCocoa之进阶篇


    前言

    由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目前只是简短的介绍了些RAC核心的一些方法,后续还需要加上MVVM+ReactiveCocoa实战开发。
    如果喜欢我的文章,可以关注我,微博:袁峥Seemygo,欢迎交流。也可以来小码哥,了解下我们的iOS培训课程。之后还会更新

    1.ReactiveCocoa常见操作方法介绍。

    • 1.1 ReactiveCocoa操作须知

      • 所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,因此只要继承RACStream就有了操作处理方法。
    • 1.2 ReactiveCocoa操作思想

      • 运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术.
      • Hook用处:截获API调用的技术。
      • Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。
    • 1.3 ReactiveCocoa核心方法bind

      • ReactiveCocoa操作的核心方法是bind(绑定),而且RAC中核心开发方式,也是绑定,之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。

      • 列如:把数据展示到控件上,之前都是重写控件的setModel方法,用RAC就可以在一开始创建控件的时候,就绑定好数据。

      • 在开发中很少使用bind方法,bind属于RAC中的底层方法,RAC已经封装了很多好用的其他方法,底层都是调用bind,用法比bind简单.

      • bind方法简单介绍和使用。
     1 // 假设想监听文本框的内容,并且在每次输出结果的时候,都在文本框的内容拼接一段文字“输出:”
     2 
     3     // 方式一:在返回结果后,拼接。
     4         [_textField.rac_textSignal subscribeNext:^(id x) {
     5 
     6             NSLog(@"输出:%@",x);
     7 
     8         }];
     9 
    10     // 方式二:在返回结果前,拼接,使用RAC中bind方法做处理。
    11     // bind方法参数:需要传入一个返回值是RACStreamBindBlock的block参数
    12     // RACStreamBindBlock是一个block的类型,返回值是信号,参数(value,stop),因此参数的block返回值也是一个block。
    13 
    14     // RACStreamBindBlock:
    15     // 参数一(value):表示接收到信号的原始值,还没做处理
    16     // 参数二(*stop):用来控制绑定Block,如果*stop = yes,那么就会结束绑定。
    17     // 返回值:信号,做好处理,在通过这个信号返回出去,一般使用RACReturnSignal,需要手动导入头文件RACReturnSignal.h。
    18 
    19     // bind方法使用步骤:
    20     // 1.传入一个返回值RACStreamBindBlock的block。
    21     // 2.描述一个RACStreamBindBlock类型的bindBlock作为block的返回值。
    22     // 3.描述一个返回结果的信号,作为bindBlock的返回值。
    23     // 注意:在bindBlock中做信号结果的处理。
    24 
    25     // 底层实现:
    26     // 1.源信号调用bind,会重新创建一个绑定信号。
    27     // 2.当绑定信号被订阅,就会调用绑定信号中的didSubscribe,生成一个bindingBlock。
    28     // 3.当源信号有内容发出,就会把内容传递到bindingBlock处理,调用bindingBlock(value,stop)
    29     // 4.调用bindingBlock(value,stop),会返回一个内容处理完成的信号(RACReturnSignal)。
    30     // 5.订阅RACReturnSignal,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
    31 
    32     // 注意:不同订阅者,保存不同的nextBlock,看源码的时候,一定要看清楚订阅者是哪个。
    33     // 这里需要手动导入#import <ReactiveCocoa/RACReturnSignal.h>,才能使用RACReturnSignal。
    34     [[_textField.rac_textSignal bind:^RACStreamBindBlock{
    35 
    36         // 什么时候调用:
    37         // block作用:表示绑定了一个信号.
    38 
    39         return ^RACStream *(id value, BOOL *stop){
    40 
    41             // 什么时候调用block:当信号有新的值发出,就会来到这个block。
    42 
    43             // block作用:做返回值的处理
    44 
    45             // 做好处理,通过信号返回出去.
    46             return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
    47         };
    48 
    49     }] subscribeNext:^(id x) {
    50 
    51         NSLog(@"%@",x);
    52 
    53     }];
    • 1.4ReactiveCocoa操作方法之映射(flattenMap,Map)

      • flattenMapMap用于把源信号内容映射成新的内容。

    flattenMap简单使用

     1    // 监听文本框的内容改变,把结构重新映射成一个新值.
     2 
     3   // flattenMap作用:把源信号的内容映射成一个新的信号,信号可以是任意类型。
     4 
     5     // flattenMap使用步骤:
     6     // 1.传入一个block,block类型是返回值RACStream,参数value
     7     // 2.参数value就是源信号的内容,拿到源信号的内容做处理
     8     // 3.包装成RACReturnSignal信号,返回出去。
     9 
    10     // flattenMap底层实现:
    11     // 0.flattenMap内部调用bind方法实现的,flattenMap中block的返回值,会作为bind中bindBlock的返回值。
    12     // 1.当订阅绑定信号,就会生成bindBlock。
    13     // 2.当源信号发送内容,就会调用bindBlock(value, *stop)
    14     // 3.调用bindBlock,内部就会调用flattenMap的block,flattenMap的block作用:就是把处理好的数据包装成信号。
    15     // 4.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
    16     // 5.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
    17 
    18 
    19 
    20     [[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
    21 
    22         // block什么时候 : 源信号发出的时候,就会调用这个block。
    23 
    24         // block作用 : 改变源信号的内容。
    25 
    26         // 返回值:绑定信号的内容.
    27         return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
    28 
    29     }] subscribeNext:^(id x) {
    30 
    31         // 订阅绑定信号,每当源信号发送内容,做完处理,就会调用这个block。
    32 
    33         NSLog(@"%@",x);
    34 
    35     }];

    Map简单使用:

     1  // 监听文本框的内容改变,把结构重新映射成一个新值.
     2 
     3     // Map作用:把源信号的值映射成一个新的值
     4 
     5     // Map使用步骤:
     6     // 1.传入一个block,类型是返回对象,参数是value
     7     // 2.value就是源信号的内容,直接拿到源信号的内容做处理
     8     // 3.把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
     9 
    10     // Map底层实现:
    11     // 0.Map底层其实是调用flatternMap,Map中block中的返回的值会作为flatternMap中block中的值。
    12     // 1.当订阅绑定信号,就会生成bindBlock。
    13     // 3.当源信号发送内容,就会调用bindBlock(value, *stop)
    14     // 4.调用bindBlock,内部就会调用flattenMap的block
    15     // 5.flattenMap的block内部会调用Map中的block,把Map中的block返回的内容包装成返回的信号。
    16     // 5.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
    17     // 6.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
    18 
    19        [[_textField.rac_textSignal map:^id(id value) {
    20         // 当源信号发出,就会调用这个block,修改源信号的内容
    21         // 返回值:就是处理完源信号的内容。
    22         return [NSString stringWithFormat:@"输出:%@",value];
    23     }] subscribeNext:^(id x) {
    24 
    25         NSLog(@"%@",x);
    26     }];
    • FlatternMap和Map的区别

      • 1.FlatternMap中的Block返回信号。
      • 2.Map中的Block返回对象。
      • 3.开发中,如果信号发出的值不是信号,映射一般使用Map
      • 4.开发中,如果信号发出的值是信号,映射一般使用FlatternMap。
    • 总结:signalOfsignals用FlatternMap。

     1     // 创建信号中的信号
     2     RACSubject *signalOfsignals = [RACSubject subject];
     3     RACSubject *signal = [RACSubject subject];
     4 
     5     [[signalOfsignals flattenMap:^RACStream *(id value) {
     6 
     7      // 当signalOfsignals的signals发出信号才会调用
     8 
     9         return value;
    10 
    11     }] subscribeNext:^(id x) {
    12 
    13         // 只有signalOfsignals的signal发出信号才会调用,因为内部订阅了bindBlock中返回的信号,也就是flattenMap返回的信号。
    14         // 也就是flattenMap返回的信号发出内容,才会调用。
    15 
    16         NSLog(@"%@aaa",x);
    17     }];
    18 
    19     // 信号的信号发送信号
    20     [signalOfsignals sendNext:signal];
    21 
    22     // 信号发送内容
    23     [signal sendNext:@1];
    • 1.5 ReactiveCocoa操作方法之组合。
      • concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
     1     RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3         [subscriber sendNext:@1];
     4 
     5         [subscriber sendCompleted];
     6 
     7         return nil;
     8     }];
     9     RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    10 
    11         [subscriber sendNext:@2];
    12 
    13         return nil;
    14     }];
    15 
    16     // 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。
    17     RACSignal *concatSignal = [signalA concat:signalB];
    18 
    19     // 以后只需要面对拼接信号开发。
    20     // 订阅拼接的信号,不需要单独订阅signalA,signalB
    21     // 内部会自动订阅。
    22     // 注意:第一个信号必须发送完成,第二个信号才会被激活
    23     [concatSignal subscribeNext:^(id x) {
    24 
    25         NSLog(@"%@",x);
    26 
    27     }];
    28 
    29     // concat底层实现:
    30     // 1.当拼接信号被订阅,就会调用拼接信号的didSubscribe
    31     // 2.didSubscribe中,会先订阅第一个源信号(signalA)
    32     // 3.会执行第一个源信号(signalA)的didSubscribe
    33     // 4.第一个源信号(signalA)didSubscribe中发送值,就会调用第一个源信号(signalA)订阅者的nextBlock,通过拼接信号的订阅者把值发送出来.
    34     // 5.第一个源信号(signalA)didSubscribe中发送完成,就会调用第一个源信号(signalA)订阅者的completedBlock,订阅第二个源信号(signalB)这时候才激活(signalB)。
    35     // 6.订阅第二个源信号(signalB),执行第二个源信号(signalB)的didSubscribe
    36     // 7.第二个源信号(signalA)didSubscribe中发送值,就会通过拼接信号的订阅者把值发送出来.

    then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号。

     1 // then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号
     2 // 注意使用then,之前信号的值会被忽略掉.
     3 // 底层实现:1、先过滤掉之前的信号发出的值。2.使用concat连接then返回的信号
     4 [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     5 
     6  [subscriber sendNext:@1];
     7  [subscriber sendCompleted];
     8  return nil;
     9 }] then:^RACSignal *{
    10  return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    11      [subscriber sendNext:@2];
    12      return nil;
    13  }];
    14 }] subscribeNext:^(id x) {
    15 
    16  // 只能接收到第二个信号的值,也就是then返回信号的值
    17  NSLog(@"%@",x);
    18 }];
    • merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
     1     // merge:把多个信号合并成一个信号
     2     //创建多个信号
     3     RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     4 
     5         [subscriber sendNext:@1];
     6 
     7 
     8         return nil;
     9     }];
    10 
    11     RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    12 
    13         [subscriber sendNext:@2];
    14 
    15         return nil;
    16     }];
    17 
    18     // 合并信号,任何一个信号发送数据,都能监听到.
    19     RACSignal *mergeSignal = [signalA merge:signalB];
    20 
    21     [mergeSignal subscribeNext:^(id x) {
    22 
    23         NSLog(@"%@",x);
    24 
    25     }];
    26 
    27     // 底层实现:
    28     // 1.合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
    29     // 2.每发出一个信号,这个信号就会被订阅
    30     // 3.也就是合并信号一被订阅,就会订阅里面所有的信号。
    31     // 4.只要有一个信号被发出就会被监听。
    • zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
     1  RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3         [subscriber sendNext:@1];
     4 
     5 
     6         return nil;
     7     }];
     8 
     9     RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    10 
    11         [subscriber sendNext:@2];
    12 
    13         return nil;
    14     }];
    15 
    16 
    17 
    18     // 压缩信号A,信号B
    19     RACSignal *zipSignal = [signalA zipWith:signalB];
    20 
    21     [zipSignal subscribeNext:^(id x) {
    22 
    23         NSLog(@"%@",x);
    24     }];
    25 
    26     // 底层实现:
    27     // 1.定义压缩信号,内部就会自动订阅signalA,signalB
    28     // 2.每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把最近

    combineLatest:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。

     1  RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3    [subscriber sendNext:@1];
     4 
     5    return nil;
     6 }];
     7 
     8 RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     9 
    10    [subscriber sendNext:@2];
    11 
    12    return nil;
    13 }];
    14 
    15 // 把两个信号组合成一个信号,跟zip一样,没什么区别
    16 RACSignal *combineSignal = [signalA combineLatestWith:signalB];
    17 
    18 [combineSignal subscribeNext:^(id x) {
    19 
    20    NSLog(@"%@",x);
    21 }];
    22 
    23 // 底层实现:
    24 // 1.当组合信号被订阅,内部会自动订阅signalA,signalB,必须两个信号都发出内容,才会被触发。
    25 // 2.并且把两个信号组合成元组发出。
    • reduce聚合:用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
     1 RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3    [subscriber sendNext:@1];
     4 
     5    return nil;
     6 }];
     7 
     8 RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     9 
    10    [subscriber sendNext:@2];
    11 
    12    return nil;
    13 }];
    14 
    15 // 聚合
    16 // 常见的用法,(先组合在聚合)。combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock
    17 // reduce中的block简介:
    18 // reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容
    19 // reduceblcok的返回值:聚合信号之后的内容。
    20 RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^id(NSNumber *num1 ,NSNumber *num2){
    21 
    22   return [NSString stringWithFormat:@"%@ %@",num1,num2];
    23 
    24 }];
    25 
    26 [reduceSignal subscribeNext:^(id x) {
    27 
    28    NSLog(@"%@",x);
    29 }];
    30 
    31 // 底层实现:
    32 // 1.订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
    • 1.6 ReactiveCocoa操作方法之过滤。

    • filter:过滤信号,使用它可以获取满足条件的信号.

    1 // 过滤:
    2 // 每次信号发出,会先执行过滤条件判断.
    3 [_textField.rac_textSignal filter:^BOOL(NSString *value) {
    4        return value.length > 3;
    5 }];
    • ignore:忽略完某些值的信号.
    1    // 内部调用filter过滤,忽略掉ignore的值
    2 [[_textField.rac_textSignal ignore:@"1"] subscribeNext:^(id x) {
    3 
    4    NSLog(@"%@",x);
    5 }];
    • distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
    1    // 过滤,当上一次和当前的值不一样,就会发出内容。
    2 // 在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新
    3 [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
    4 
    5    NSLog(@"%@",x);
    6 }];
    • take:从开始一共取N次的信号
     1 // 1、创建信号
     2 RACSubject *signal = [RACSubject subject];
     3 
     4 // 2、处理信号,订阅信号
     5 [[signal take:1] subscribeNext:^(id x) {
     6 
     7    NSLog(@"%@",x);
     8 }];
     9 
    10 // 3.发送信号
    11 [signal sendNext:@1];
    12 
    13 [signal sendNext:@2];
    14 
    15 
    
    • takeLast:取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号.
     1 // 1、创建信号
     2 RACSubject *signal = [RACSubject subject];
     3 
     4 // 2、处理信号,订阅信号
     5 [[signal takeLast:1] subscribeNext:^(id x) {
     6 
     7    NSLog(@"%@",x);
     8 }];
     9 
    10 // 3.发送信号
    11 [signal sendNext:@1];
    12 
    13 [signal sendNext:@2];
    14 
    15 [signal sendCompleted];
    • takeUntil:(RACSignal *):获取信号直到某个信号执行完成

    1 // 监听文本框的改变直到当前对象被销毁

    2 [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal]; 

    • skip:(NSUInteger):跳过几个信号,不接受。
    1 // 表示输入第一次,不会被监听到,跳过第一次发出的信号
    2 [[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
    3 
    4    NSLog(@"%@",x);
    5 }];
    • switchToLatest:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
     1 RACSubject *signalOfSignals = [RACSubject subject];
     2 RACSubject *signal = [RACSubject subject];
     3 
     4 // 获取信号中信号最近发出信号,订阅最近发出的信号。
     5 // 注意switchToLatest:只能用于信号中的信号
     6 [signalOfSignals.switchToLatest subscribeNext:^(id x) {
     7 
     8    NSLog(@"%@",x);
     9 }];
    10 [signalOfSignals sendNext:signal];
    11 [signal sendNext:@1];
    12 
    

    1.7 ReactiveCocoa操作方法之秩序。

    • doNext: 执行Next之前,会先执行这个Block
    • doCompleted: 执行sendCompleted之前,会先执行这个Block

     1 [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2   [subscriber sendNext:@1];
     3   [subscriber sendCompleted];
     4   return nil;
     5 }] doNext:^(id x) {
     6 // 执行[subscriber sendNext:@1];之前会调用这个Block
     7   NSLog(@"doNext");;
     8 }] doCompleted:^{
     9    // 执行[subscriber sendCompleted];之前会调用这个Block
    10   NSLog(@"doCompleted");;
    11 
    12 }] subscribeNext:^(id x) {
    13 
    14   NSLog(@"%@",x);
    15 }];
    16 
    
     
    • 1.8 ReactiveCocoa操作方法之线程。

      • deliverOn: 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。

      • subscribeOn: 内容传递和副作用都会切换到制定线程中。

    • 1.9 ReactiveCocoa操作方法之时间。

      • timeout:超时,可以让一个信号在一定的时间后,自动报错。

     1 RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2  return nil;
     3 }] timeout:1 onScheduler:[RACScheduler currentScheduler]];
     4 
     5 [signal subscribeNext:^(id x) {
     6 
     7  NSLog(@"%@",x);
     8 } error:^(NSError *error) {
     9  // 1秒后会自动调用
    10  NSLog(@"%@",error);
    11 }];

    interval 定时:每隔一段时间发出信号 

    1 [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
    2 
    3  NSLog(@"%@",x);
    4 }];

    delay 延迟发送next。

     1  RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3  [subscriber sendNext:@1];
     4  return nil;
     5 }] delay:2] subscribeNext:^(id x) {
     6 
     7  NSLog(@"%@",x);
     8 }];
     9 
    
    • 1.9 ReactiveCocoa操作方法之重复。

      • retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.
     1      __block int i = 0;
     2     [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     3 
     4             if (i == 10) {
     5                 [subscriber sendNext:@1];
     6             }else{
     7                 NSLog(@"接收到错误");
     8                 [subscriber sendError:nil];
     9             }
    10             i++;
    11 
    12         return nil;
    13 
    14     }] retry] subscribeNext:^(id x) {
    15 
    16         NSLog(@"%@",x);
    17 
    18     } error:^(NSError *error) {
    19 
    20 
    21     }];
    22 
    • replay重放:当一个信号被多次订阅,反复播放内容
     1         RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     2 
     3 
     4         [subscriber sendNext:@1];
     5         [subscriber sendNext:@2];
     6 
     7         return nil;
     8     }] replay];
     9 
    10     [signal subscribeNext:^(id x) {
    11 
    12         NSLog(@"第一个订阅者%@",x);
    13 
    14     }];
    15 
    16     [signal subscribeNext:^(id x) {
    17 
    18         NSLog(@"第二个订阅者%@",x);
    19 
    20     }];
    21 
    

    throttle节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

     1    RACSubject *signal = [RACSubject subject];
     2 
     3 _signal = signal;
     4 
     5 // 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
     6 [[signal throttle:1] subscribeNext:^(id x) {
     7 
     8    NSLog(@"%@",x);
     9 }];
    10 
    11 
    

    2.介绍MVVM架构思想。

    2.1 程序为什么要架构:便于程序员开发和维护代码。

    2.2 常见的架构思想:

    • MVC M:模型 V:视图 C:控制器

    • MVVM M:模型 V:视图+控制器 VM:视图模型

    • MVCS M:模型 V:视图 C:控制器 C:服务类

    • VIPER V:视图 I:交互器 P:展示器 E:实体 R:路由
      PS:VIPER架构思想

    2.3 MVVM介绍

    • 模型(M):保存视图数据。

    • 视图+控制器(V):展示内容 + 如何展示

    • 视图模型(VM):处理展示的业务逻辑,包括按钮的点击,数据的请求和解析等等。

    3.ReactiveCocoa + MVVM 实战一:登录界面

    • 3.1需求+分析+步骤
     1 /* 需求:1.监听两个文本框的内容,有内容才允许按钮点击
     2         2.默认登录请求.
     3 
     4    用MVVM:实现,之前界面的所有业务逻辑
     5    分析:1.之前界面的所有业务逻辑都交给控制器做处理
     6         2.在MVVM架构中把控制器的业务全部搬去VM模型,也就是每个控制器对应一个VM模型.
     7 
     8    步骤:1.创建LoginViewModel类,处理登录界面业务逻辑.
     9         2.这个类里面应该保存着账号的信息,创建一个账号Account模型
    10         3.LoginViewModel应该保存着账号信息Account模型。
    11         4.需要时刻监听Account模型中的账号和密码的改变,怎么监听?
    12         5.在非RAC开发中,都是习惯赋值,在RAC开发中,需要改变开发思维,由赋值转变为绑定,可以在一开始初始化的时候,就给Account模型中的属性绑定,并不需要重写set方法。
    13         6.每次Account模型的值改变,就需要判断按钮能否点击,在VM模型中做处理,给外界提供一个能否点击按钮的信号.
    14         7.这个登录信号需要判断Account中账号和密码是否有值,用KVO监听这两个值的改变,把他们聚合成登录信号.
    15         8.监听按钮的点击,由VM处理,应该给VM声明一个RACCommand,专门处理登录业务逻辑.
    16         9.执行命令,把数据包装成信号传递出去
    17         10.监听命令中信号的数据传递
    18         11.监听命令的执行时刻
    19  */
    20 
    21 
    
    • 3.2 控制器的代码
     1 @interface ViewController ()
     2 
     3 @property (nonatomic, strong) LoginViewModel *loginViewModel;
     4 
     5 @property (weak, nonatomic) IBOutlet UITextField *accountField;
     6 @property (weak, nonatomic) IBOutlet UITextField *pwdField;
     7 
     8 @property (weak, nonatomic) IBOutlet UIButton *loginBtn;
     9 
    10 
    11 @end
    12 
    13 - (LoginViewModel *)loginViewModel
    14 {
    15     if (_loginViewModel == nil) {
    16 
    17         _loginViewModel = [[LoginViewModel alloc] init];
    18     }
    19     return _loginViewModel;
    20 }
    21 
    22 // 视图模型绑定
    23 - (void)bindModel
    24 {
    25     // 给模型的属性绑定信号
    26     // 只要账号文本框一改变,就会给account赋值
    27     RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
    28     RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
    29 
    30     // 绑定登录按钮
    31     RAC(self.loginBtn,enabled) = self.loginViewModel.enableLoginSignal;
    32 
    33    // 监听登录按钮点击
    34     [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    35 
    36         // 执行登录事件
    37         [self.loginViewModel.LoginCommand execute:nil];
    38     }];
    39 }
    40 
    41 
    
    • 3.3 VM的代码
     1 @interface LoginViewModel : NSObject
     2 
     3 @property (nonatomic, strong) Account *account;
     4 
     5 
     6 // 是否允许登录的信号
     7 @property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
     8 
     9 @property (nonatomic, strong, readonly) RACCommand *LoginCommand;
    10 
    11 @end
    12 
    13 @implementation LoginViewModel
    14 - (Account *)account
    15 {
    16     if (_account == nil) {
    17         _account = [[Account alloc] init];
    18     }
    19     return _account;
    20 }
    21 - (instancetype)init
    22 {
    23     if (self = [super init]) {
    24         [self initialBind];
    25     }
    26     return self;
    27 }
    28 
    29 
    30 // 初始化绑定
    31 - (void)initialBind
    32 {
    33     // 监听账号的属性值改变,把他们聚合成一个信号。
    34     _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
    35 
    36         return @(account.length && pwd.length);
    37 
    38     }];
    39 
    40     // 处理登录业务逻辑
    41     _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    42 
    43         NSLog(@"点击了登录");
    44         return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    45 
    46             // 模仿网络延迟
    47             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    48 
    49                 [subscriber sendNext:@"登录成功"];
    50 
    51                 // 数据传送完毕,必须调用完成,否则命令永远处于执行状态
    52                 [subscriber sendCompleted];
    53             });
    54 
    55             return nil;
    56         }];
    57     }];
    58 
    59     // 监听登录产生的数据
    60     [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
    61 
    62         if ([x isEqualToString:@"登录成功"]) {
    63             NSLog(@"登录成功");
    64         }
    65     }];
    66 
    67     // 监听登录状态
    68     [[_LoginCommand.executing skip:1] subscribeNext:^(id x) {
    69         if ([x isEqualToNumber:@(YES)]) {
    70 
    71             // 正在登录ing...
    72             // 用蒙版提示
    73             [MBProgressHUD showMessage:@"正在登录..."];
    74 
    75 
    76         }else
    77         {
    78             // 登录成功
    79             // 隐藏蒙版
    80             [MBProgressHUD hideHUD];
    81         }
    82     }];
    83 }

    4.ReactiveCocoa + MVVM 实战二:网络请求数据

    • 4.1 接口:这里先给朋友介绍一个免费的网络数据接口,豆瓣。可以经常用来练习一些网络请求的小Demo.

    • 4.2 需求+分析+步骤

     1 /*
     2     需求:请求豆瓣图书信息,url:https://api.douban.com/v2/book/search?q=基础
     3 
     4     分析:请求一样,交给VM模型管理
     5 
     6     步骤:
     7         1.控制器提供一个视图模型(requesViewModel),处理界面的业务逻辑
     8         2.VM提供一个命令,处理请求业务逻辑
     9         3.在创建命令的block中,会把请求包装成一个信号,等请求成功的时候,就会把数据传递出去。
    10         4.请求数据成功,应该把字典转换成模型,保存到视图模型中,控制器想用就直接从视图模型中获取。
    11         5.假设控制器想展示内容到tableView,直接让视图模型成为tableView的数据源,把所有的业务逻辑交给视图模型去做,这样控制器的代码就非常少了。
    12  */
    13 
    
    • 4.3控制器代码
     1 @interface ViewController ()
     2 
     3 @property (nonatomic, weak) UITableView *tableView;
     4 
     5 @property (nonatomic, strong) RequestViewModel *requesViewModel;
     6 
     7 
     8 @end
     9 
    10 @implementation ViewController
    11 - (RequestViewModel *)requesViewModel
    12 {
    13     if (_requesViewModel == nil) {
    14         _requesViewModel = [[RequestViewModel alloc] init];
    15     }
    16     return _requesViewModel;
    17 }
    18 
    19 - (void)viewDidLoad {
    20     [super viewDidLoad];
    21     // Do any additional setup after loading the view, typically from a nib.
    22 
    23     // 创建tableView
    24     UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    25     tableView.dataSource = self.requesViewModel;
    26 
    27     [self.view addSubview:tableView];
    28 
    29     // 执行请求
    30  RACSignal *requesSiganl = [self.requesViewModel.reuqesCommand execute:nil];
    31 
    32    // 获取请求的数据
    33     [requesSiganl subscribeNext:^(NSArray *x) {
    34 
    35         self.requesViewModel.models = x;
    36 
    37         [self.tableView reloadData];
    38 
    39     }];
    40 
    41 }
    42 
    43 
    44 @end
    • 4.4视图模型(VM)代码
     1 @interface RequestViewModel : NSObject<UITableViewDataSource>
     2 
     3 
     4     // 请求命令
     5     @property (nonatomic, strong, readonly) RACCommand *reuqesCommand;
     6 
     7     //模型数组
     8     @property (nonatomic, strong, readonly) NSArray *models;
     9 
    10 
    11 
    12 @end
    13 
    14 @implementation RequestViewModel
    15 
    16 - (instancetype)init
    17 {
    18     if (self = [super init]) {
    19 
    20         [self initialBind];
    21     }
    22     return self;
    23 }
    24 
    25 
    26 - (void)initialBind
    27 {
    28     _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    29 
    30         RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    31 
    32 
    33             NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    34             parameters[@"q"] = @"基础";
    35 
    36             // 发送请求
    37             [[AFHTTPRequestOperationManager manager] GET:@"https://api.douban.com/v2/book/search" parameters:parameters success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
    38                 NSLog(@"%@",responseObject);
    39 
    40                 // 请求成功调用
    41                 // 把数据用信号传递出去
    42                 [subscriber sendNext:responseObject];
    43 
    44                 [subscriber sendCompleted];
    45 
    46 
    47             } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
    48                 // 请求失败调用
    49 
    50             }];
    51 
    52             return nil;
    53         }];
    54 
    55 
    56 
    57 
    58         // 在返回数据信号时,把数据中的字典映射成模型信号,传递出去
    59         return [requestSignal map:^id(NSDictionary *value) {
    60             NSMutableArray *dictArr = value[@"books"];
    61 
    62             // 字典转模型,遍历字典中的所有元素,全部映射成模型,并且生成数组
    63             NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
    64 
    65                 return [Book bookWithDict:value];
    66             }] array];
    67 
    68             return modelArr;
    69         }];
    70 
    71     }];
    72 
    73  }
    74 
    75 #pragma mark - UITableViewDataSource
    76 
    77 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    78 {
    79     return self.models.count;
    80 }
    81 
    82 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    83 {
    84     static NSString *ID = @"cell";
    85     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    86     if (cell == nil) {
    87 
    88         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    89     }
    90 
    91     Book *book = self.models[indexPath.row];
    92     cell.detailTextLabel.text = book.subtitle;
    93     cell.textLabel.text = book.title;
    94 
    95     return cell;
    96 }
    97 
    98 @end
     
     
    文/袁峥Seemygo(简书作者)
    原文链接:http://www.jianshu.com/p/e10e5ca413b7
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
     
     
  • 相关阅读:
    eclipse里maven项目An error occurred while filtering resources解决办法
    python中分页使用
    es学习
    cmdb资产管理2
    saltstack安装使用
    Django Rest Framework
    免交互批量分发公钥的实现
    单链表复制早已难不到你,但若我们再加个指针...
    面试 16:栈的压入压出队列(剑指 Offer 第 22 题)
    面试 15:顺时针从外往里打印数字(剑指 Offer 第 20 题)
  • 原文地址:https://www.cnblogs.com/oc-bowen/p/5895846.html
Copyright © 2020-2023  润新知