美文网首页
从计算器到ReactiveCocoa

从计算器到ReactiveCocoa

作者: jimi_nice | 来源:发表于2018-11-23 10:22 被阅读0次

    ReactiveCocoa是github开源的一个函数式响应式编程(FRP)框架

    • 函数式吶应式编程(FRP)

      函数式响应式,这里面包含了两个编程风格.即函数式(Functional Programming)和响应式(Reactive Programming),这里先介绍函数式,在说函数式之前先介绍链式编程.

      • 链式编程

        是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3),常用框架Masonry.我们的主题是从计算器到ReactiveCocoa,就拿计算器为例了。

        @interface CalculatorMaker : NSObject
        @property (nonatomic, assign) int result;
        //加法
        - (CalculatorMaker *(^)(int value))add;
        //减法
        - (CalculatorMaker *(^)(int value))sub;
        //归0
        - (CalculatorMaker *(^)(void))clean;
        
        - (void)makeCalculator:(void(^)(CalculatorMaker *mark))markBlock;
        @end
        
        @implementation CalculatorMaker
        - (CalculatorMaker *(^)(int value))add {
            return ^CalculatorMaker *(int a) {
                self.result += a;
                return self;
            };
        }
        
        - (CalculatorMaker *(^)(int value))sub {
            return ^CalculatorMaker *(int a) {
                self.result -= a;
                return self;
            };
        }
        
        - (CalculatorMaker *(^)(void))clean {
            return ^CalculatorMaker *(void) {
                self.result = 0;
                return self;
            };
        }
        
        - (void)makeCalculator:(void(^)(CalculatorMaker *mark))markBlock {
            markBlock(self);
        }
        @end
        
        //sample
        CalculatorMaker *calculator = [[CalculatorMaker alloc] init];
        [calculator makeCalculator:^(CalculatorMaker *mark) {
            mark.add(2).sub(1).clean().add(3);
        }];
        NSLog(@"~~~~ value %d",calculator.result);
        /*output
          ~~~~ value 3
        */
        

        这里要问一下为什么能通过.操作符连接操作,回想一下.操作符在OC中实际是防问属性的get和set方法的一种简写.所以我们来拆开

        CalculatorMaker *calculator = [[CalculatorMaker alloc] init];
        [calculator makeCalculator:^(CalculatorMaker *mark) {
            CalculatorMaker *(^tmpAddBlock)(int) = [mark add];
            CalculatorMaker *tmpAddReturn = tmpAddBlock(2);
        
            CalculatorMaker *(^tmpSubBlock)(int) = [tmpAddReturn sub];
            CalculatorMaker *tmpSubReturn = tmpSubBlock(1);
        
            CalculatorMaker *(^tmpCleanBlock)(void) = [tmpSubReturn clean];
            CalculatorMaker *tmpCleanReturn = tmpCleanBlock();
        
            CalculatorMaker *(^tmpAdd2Block)(int) = [tmpCleanReturn add];
            CalculatorMaker *tmpAdd2Return = tmpAdd2Block(3);
        }];
        NSLog(@"~~~~ value %d",calculator.result);
        /*output
          ~~~~ value 3
        */    
        //所以两者结果是完全一样的
        

        接下来介绍函数式编程,在介绍这个之前先介绍两个很有意思的概念

        • side effects(副作用)

          副作用是在计算结果的过程中,系统状态的一种改变,或是外部世界可观察的交互作用。比如,更改档案系统,发送http请求等,只要与function外部环境发生交互作用的都是副作用。

        • pure function(纯函数)

          定义:相同输入,永远会得到相同的输出,而且没有任何显著副作用.

          扩展解释:函数与外界交互只有唯二通道,参数、返回值(输入,输出)。同时参数应该是只读的,函数不应该修改参数内部数据,以下举几个例子

          //例一:相同输入,永远会得到相同的输出,没有副作用
          var min = 1;
          var OuterCompareNumber = function(number) {
            return number > min;
          }
          
          var InnerCompareNumber = function(number) {
            var min = 1;
            return number > min;
          }
          //OuterCompareNumber非纯函数.min可能会被外界改变
          //InnerCompareNumber是纯函数
          
          //例二:没有任何显著副作用
          var callRequest = function(url,paramas) {
            return $.getJSON(url, paramas);
          }
          
          var delayCallRequest = function(url,paramas) {
            return function() {
              return $.getJSON(url, paramas);
            }
          }
          
          /*
          callRequest(url, paramas);
          delayCallRequest(url, paramas)();
          callRequest直接返回了一个http请求,那么这个是与外界交互,
          返回的结果有太大的不确定性,所以是impure的函数;
          而delayCallRequest采用了延迟执行的方式,返回了一个函数,
          只有调用这个函数的时候才会发送请求:delayCallRequest(url, params)(),
          但就delayCallRequest而言,传相同的参数,得到的结果是一样的,是相同参数的函数,
          所以这个是一个pure的函数。
          */
          

          先简单了解一下这两个概念,下面会有用到。纯函数有如下好处.
          1.无状态,Stateless。线程安全。不需要线程同步。
          2.Pure Function相互调用组装起来的函数,还是Pure Function。
          3.应用程序或者运行环境(Runtime)可以对Pure Function的运算结果进行缓存,运算加快速度。
          我们继续了解函数式编

    • 函数式编程

      函数是第一等公民,能像其它普通变量一样去,创建,修改,传递,我们看可以把block看成函数

      @interface CalculatorFP : NSObject
      @property (nonatomic, assign) int result;
      - (CalculatorFP *)add:(int(^)(void))block;
      - (CalculatorFP *)sub:(int(^)(void))block;
      - (CalculatorFP *)clean;
      @end
      
      @implementation CalculatorFP
      - (CalculatorFP *)add:(int(^)(void))block {
         int value = block();
         self.result += value;
         return self;
      }
      
      - (CalculatorFP *)sub:(int(^)(void))block {
         int value = block();
         self.result -= value;
         return self;
      }
      
      - (CalculatorFP *)clean {
         self.result = 0;
         return self;
      }
      @end
      
      //sample
      CalculatorFP *fp = [[CalculatorFP alloc] init];
      [[[[fp add:^int{
         return 2;
      }] sub:^int{
         return 1;
      }] clean] add:^int{
         return 3;
      }];
      
      NSLog(@"~~~~ fp value %d",fp.result);
      /*output
       ~~~~ fp value 3
      */
      

      上面的计算器的例子,已经完全够用。然后我们扩展思考一下。假如,我们想表示一个计算表达式如 int c = a + b;并且在a或b值改变的时候.c能同步改变。怎么做?首先我们要实现a,b改变的时候得到通知,所以我们介绍另一个编程思想,响应式编程

    • 响应式编程

      直接上代码

        @interface CalculatorReactive : NSObject
        @property (nonatomic, assign) int value;
        - (void)addObserverValueUpdate:(void(^)(int a))block;
        //这个方法返回一个block,调用该block,就能移除block的监听
        - (void(^)(void))addWithRemoveControllerObserverValueUpdate:(void(^)(int))block;
        @end
      
        @interface CalculatorReactive ()
        @property (nonatomic, strong) NSMutableArray<void (^)(int)> *recordObserverUpdateBlockArray;
        @end
      
        @implementation CalculatorReactive
        - (id)init {
            if (self = [super init]) {
                self.recordObserverUpdateBlockArray = [NSMutableArray array];
            }
            return self;
        }
      
        - (void)addObserverValueUpdate:(void(^)(int))block {
            [_recordObserverUpdateBlockArray addObject:block];
        }
      
        - (void(^)(void))addWithRemoveControllerObserverValueUpdate:(void(^)(int))block {
            void(^removeBlock)(void) = ^{
                [_recordObserverUpdateBlockArray removeObject:block];
            };
      
            [_recordObserverUpdateBlockArray addObject:block];
            return removeBlock;
        }
      
        - (void)setValue:(int)value {
            if (_value != value) {
                _value = value;
                [_recordObserverUpdateBlockArray enumerateObjectsUsingBlock:^(void (^ _Nonnull obj)(int), NSUInteger idx, BOOL * _Nonnull stop) {
                    obj(value);
                }];
            }
        }
        @end
      
        //sample
        CalculatorReactive *a = [[CalculatorReactive alloc] init];
        [a addObserverValueUpdate:^(int v) {
            NSLog(@"a new value is %d",v);
        }];
      
        void(^removeBlock)(void) = [a addWithRemoveControllerObserverValueUpdate:^(int v) {
            NSLog(@"a with remove new value %d",v);
        }];
      
        a.value = 10;
        a.value = 5;
        removeBlock();
        a.value = 11;
        /*output
        a new value is 10
        a with remove new value 10
        a new value is 5
        a with remove new value 5
        a new value is 11
        */
      

      OK,为了达成int c = a + b;完成了第一步,我们能监听a,b变化了。为了进一步完成目前我们把响应式和函数式结合一起。

    • 函数式响应式编程

      @interface CalculatorReactiveFP : CalculatorReactive
      - (CalculatorReactiveFP *)addByOther:(CalculatorReactiveFP *)other;
      - (CalculatorReactiveFP *)subByOther:(CalculatorReactiveFP *)other;
      @end
      
      @implementation CalculatorReactiveFP
      - (void)commondDoBlock:(void(^)(void))block other:(CalculatorReactiveFP *)other {
          void(^tmpObserverValueUpdateBlock)(int) = ^(int a) {
              block();
          };
      
          [self.recordObserverUpdateBlockArray addObject:tmpObserverValueUpdateBlock];
          [other.recordObserverUpdateBlockArray addObject:tmpObserverValueUpdateBlock];
      }
      
      - (CalculatorReactiveFP *)addByOther:(CalculatorReactiveFP *)other {
          CalculatorReactiveFP *result = [[CalculatorReactiveFP alloc] init];
      
          [self commondDoBlock:^void{
              int newValue = self.value + other.value;
              result.value = newValue;
          } other:other];
      
          return result;
      }
      
      - (CalculatorReactiveFP *)subByOther:(CalculatorReactiveFP *)other {
          CalculatorReactiveFP *result = [[CalculatorReactiveFP alloc] init];
      
          [self commondDoBlock:^void{
              int newValue = self.value - other.value;
              result.value = newValue;
          } other:other];
      
          return result;
      }
      @end
      
      //sample
      CalculatorReactiveFP *a = [[CalculatorReactiveFP alloc] init];
      [a addObserverValueUpdate:^(int v) {
          NSLog(@"a new value is %d",v);
      }];
      CalculatorReactiveFP *b = [[CalculatorReactiveFP alloc] init];
      [b addObserverValueUpdate:^(int v) {
          NSLog(@"b new value is %d",v);
      }];
      CalculatorReactiveFP *c = [a addByOther:b];
      [c addObserverValueUpdate:^(int v) {
          NSLog(@"c new value is %d",v);
      }];
      
      CalculatorReactiveFP *e = [[c addByOther:a] subByOther:b];
      [e addObserverValueUpdate:^(int v) {
          NSLog(@"e new value is %d",v);
      }];
      
      a.value = 1;
      b.value = 3;
      a.value = 5;
      c.value = 11;
      b.value = 2;
      
      /*output
        a new value is 1
        c new value is 1
        e new value is 2
        b new value is 3
        c new value is 4
        a new value is 5
        c new value is 8
        e new value is 10
        c new value is 11
        e new value is 13
        b new value is 2
        c new value is 7
        e new value is 10
      */
      

      我们基本实现了int c = b + a;这种运用算,用到了函数式响应式编程.ReactiveCocoa也是函数式响应式编程,当然我们跟他差很多,但是核心思想是一样,我们关注结果,不关注具体过程.这非常重要,请一定记住,响应式编程的重点是关注事务的关系.不关注过程是怎么样进行的。

      为了更接近ReactiveCocoa,我开始进一步优化,第一步.我们要的不是只支持计算器,要支持任何数据类型,OC中id类型就是泛型.同时作为管道我们不需要去记录传递过来值。接下来,我们离开计算器,我们以Cocoa作为类名前缀.

      //为了方便阅读,把一些常的block加上别名
      typedef void(^CCSubscribeBlock)(id value);
      typedef void(^CCBlankBlock)(void);
      
      @interface CocoaReactive : NSObject
      //把方法名,由set/observer->send/subscribe
      - (void)sendValue:(id)value;
      - (CCBlankBlock)subscribeValue:(CCSubscribeBlock)block;
      @end
      
      @interface CocoaReactive ()
      @property (nonatomic, strong) NSMutableArray<CCSubscribeBlock> *recordObserverUpdateBlockArray;
      @end
      
      @implementation CocoaReactive
      - (id)init {
          if (self = [super init]) {
              self.recordObserverUpdateBlockArray = [NSMutableArray array];
          }
          return self;
      }
      
      - (void)sendValue:(id)value {
          [_recordObserverUpdateBlockArray enumerateObjectsUsingBlock:^(void (^ _Nonnull obj)(id), NSUInteger idx, BOOL * _Nonnull stop) {
              obj(value);
          }];
      }
      
      - (CCBlankBlock)subscribeValue:(CCSubscribeBlock)block {
          CCBlankBlock removeBlock = ^{
              [_recordObserverUpdateBlockArray removeObject:block];
          };
      
          [_recordObserverUpdateBlockArray addObject:block];
          return removeBlock;
      }
      @end
      
      @interface CocoaReactiveFP : CocoaReactive
      - (CocoaReactiveFP *)processValue:(id(^)(id v))block;
      @end
      @implementation CocoaReactiveFP
      - (CocoaReactiveFP *)processValue:(id(^)(id))block {
          CocoaReactiveFP *result = [[CocoaReactiveFP alloc] init];
          void(^tmpSubscribeBlock)(id) = ^(id a) {
              id newValue = block(a);
              [result sendValue:newValue];
          };
          [self subscribeValue:tmpSubscribeBlock];
          return result;
      }
      @end
      
      //sample
      CocoaReactiveFP *a = [[CocoaReactiveFP alloc] init];
      
      CocoaReactiveFP *b = [[a processValue:^id(id v) {
          int tmpV = [v intValue];
          return @(tmpV + 3);
      }] processValue:^id(id v) {
          int tmpV = [v intValue];
          return @(tmpV + 5);
      }];
      
      [b subscribeValue:^(id v) {
          NSLog(@"b new value %@",v);
      }];
      
      [a sendValue:@(2)];;
      /*output
        b new value 10
      */
      

      上面的例子,可以清楚的看到流程:a收到一个值,b就会在这基础上+3再+5,然后再返回这个值给b.我们大概实现了一个管道作用于另一个管道。即 b = a + (N个block);我们能否实现 c = a + b;,a,b同时是管道呢,当然能,我们返回值变成一个管道试试。我们搞个分类

      @interface CocoaReactiveFP (optimize)
      - (CocoaReactiveFP *)processValue2:(CocoaReactiveFP *(^)(id v))block;
      @end
      
      @implementation CocoaReactiveFP (optimize)
      - (CocoaReactiveFP *)processValue2:(CocoaReactiveFP *(^)(id v))block {
          CocoaReactiveFP *result = [[CocoaReactiveFP alloc] init];
          void(^tmpSubscribeBlock)(id) = ^(id a) {
              //这里返回了一个管道,我们订阅这个管道,当它有值过来,我们就传给result
              CocoaReactiveFP *reactive = block(a);
              void(^tmpSubscribeReactiveBlock)(id) = ^(id a) {
                  [result sendValue:a];
              };
              [reactive subscribeValue:tmpSubscribeReactiveBlock];
          };
          [self subscribeValue:tmpSubscribeBlock];
          return result;
      }
      @end
      
      //sample
      CocoaReactiveFP *a = [[CocoaReactiveFP alloc] init];
      CocoaReactiveFP *b = [[CocoaReactiveFP alloc] init];
      
      CocoaReactiveFP *c = [a processValue2:^CocoaReactiveFP *(id v) {
          return b;
      }];
      
      [c subscribeValue:^(id v) {
          NSLog(@"c new value %@",v);
      }];
      
      [a sendValue:@(2)];
      [b sendValue:@(20)];
      
      /*output
        c new value 20
      */
      

      我们实现返回一个全新的管道.然后订阅它,它的值传过来后我们传给我们要的管道。但是这段代码怎么看都不舒服,它有以下三个问题.

      1. 不够Lazy,管道b直接就初始化出来的,我希望,应该是[c subscribeValue:...];时候创建
      2. 管道a的值被无视了,直接无用了。
      3. 回顾一下我上面提到的side effects/pure function.processValue2的参数block,不是prue function。它防问了外部变量b,产生了副作用.怎么改呢,可以参考上面的例子,将防问外部变量打包到一个函数中,然后将这个函数作为返回值.这里的函数即我们的block.我们动手改吧,
      
      @class CocoaReactiveFP;
      //方便阅读我们将方法processValue2 的参数block加别名
      typedef CocoaReactiveFP *(^CCBindBlock)(id value);
      @interface CalculatorReactiveFP (Lazy)
      - (CocoaReactiveFP *)processValueLazy:(CCBindBlock(^)(void))block;
      @end
      
      @implementation CocoaReactiveFP (Lazy)
      - (CocoaReactiveFP *)processValueLazy:(CCBindBlock(^)(void))block {
          CocoaReactiveFP *result = [[CocoaReactiveFP alloc] init];
          void(^tmpSubscribeBlock)(id) = ^(id a) {
              //block将在有subscribe的时候才调用
              CCBindBlock bindBlock = block();
              CocoaReactiveFP *bindReactive = bindBlock(a);
              void(^tmpBindReactiveSubscribeBlock)(id) = ^(id a) {
                  [result sendValue:a];
              };
              [bindReactive subscribeValue:tmpBindReactiveSubscribeBlock];
          };
          [self subscribeValue:tmpSubscribeBlock];
          return result;
      }
      @end
      
      //sample
      CocoaReactiveFP *aLazy = [[CocoaReactiveFP alloc] init];
      
      CocoaReactiveFP *cLazy = [aLazy processValueLazy:^CCBindBlock{
          return ^CocoaReactiveFP *(id v){
              CocoaReactiveFP *bLazy = [[CocoaReactiveFP alloc]init];
              //尴尬的我们发现bLazy,无处sendValue.在这sendValue的话,
              //cLazy还未subscribe bLazy.
              return bLazy;
          };
      }];
      
      [cLazy subscribeValue:^(id value) {
          NSLog(@"cLazy new value %@",value);
      }];
      
      [aLazy sendValue:@(2)];
      [aLazy sendValue:@(5)];
      

      看到上面好尴尬,我们无法对bLazy调用sendValue,想了想,我们需要给bLazy开个block,当有人订阅它的时候,通知我们,我们就可以在那个block里面sendValue,马上动手

      @interface CocoaReactiveFP (Lazy2)
      - (CocoaReactiveFP *)createReactiveFPLazy:(void(^)(CCSubscribeBlock o))block;
      @end
      
      @interface CocoaReactiveFP ()
      //
      @property (nonatomic, copy) void (^createBlock)(CCSubscribeBlock o);
      @end
      
      @implementation CocoaReactiveFP (Lazy2)
      - (CocoaReactiveFP *)createReactiveFPLazy:(void(^)(CCSubscribeBlock o))block {
          CocoaReactiveFP *lazy = [[CocoaReactiveFP alloc] init];
          lazy.createBlock = block;
          return lazy;
      }
      
      - (CCBlankBlock)subscribeValue:(CCSubscribeBlock)block {
          CCBlankBlock result = [super subscribeValue:block];
          //当有人subscribe的时候我们就调用_createBlock通知外界
          if (_createBlock) {
              _createBlock(block);
          }
          return result;
      }
      @end
      
      //sample
      CocoaReactiveFP *aLazy = [[CocoaReactiveFP alloc] init];
      
      CocoaReactiveFP *cLazy = [aLazy processValueLazy:^CCBindBlock{
          return ^CocoaReactiveFP *(id v){
              CocoaReactiveFP *bLazy = [CocoaReactiveFP createReactiveFPLazy:^(CCSubscribeBlock o) {
                  o(@([v intValue] + 5));
              }];
              return bLazy;
          };
      }];
      
      [cLazy subscribeValue:^(id value) {
          NSLog(@"cLazy new value %@",value);
      }];
      
      [aLazy sendValue:@(2)];
      [aLazy sendValue:@(5)];
      /*output
        cLazy new value 7
        cLazy new value 10
      */
      

      很好,这么写解决了上面三个问题,bLazy,是lazy create.processValueLazy的参数block没有副作用,是纯函数.a管道的值也得到利用了.总体来说,还算不错.我们还有最后一步

      我们重新思考一下函数式响应式编程.首先它由两种编程风格结合在一起,在OC单继承语种上,他们是有先后的,我们上面的例子,是吶应式在前,函数式在后.在ReactiveCocoa中,是相反的,函数式在前,响应式在后.万物皆是流,我们改一下.同时有些微调。

      @interface CocoaSubscriber : NSObject
      + (instancetype)subscriberWithValueBlock:(CCSubscribeBlock)block;
      - (void)sendValue:(id)value;
      @end
      
      @class CocoaStream;
      typedef CocoaStream *(^CCStreamBindBlock)(id value);
      @interface CocoaStream : NSObject
      - (__kindof CocoaStream *)bind:(CCStreamBindBlock(^)(void))block;
      @end
      
      @interface CocoaSignal : CocoaStream
      - (CocoaSignal *)createSignal:(void(^)(CocoaSubscriber *subscriber))createBlock;
      - (CCBlankBlock)subscribeValue:(CCSubscribeBlock)block;
      @end
      
      @interface CocoaSubscriber ()
      @property (nonatomic, copy) CCSubscribeBlock valueBlock;
      @property (nonatomic, readonly) CCBlankBlock dispose;
      @end
      
      @implementation CocoaSubscriber
      - (instancetype)subscriberWithValueBlock:(CCSubscribeBlock)block {
          CocoaSubscriber *result  = [[CocoaSubscriber alloc] init];
          result.valueBlock = block;
          return result;
      }
      
      - (id)init {
          if (self = [super init]) {
              __weak CocoaSubscriber *wself = self;
              _dispose = ^{
                  __weak CCSubscribeBlock wblock = wself.valueBlock;
                  if (wblock) {
                      wblock = NULL;
                  }
              };
          }
          return self;
      }
      
      - (void)sendValue:(id)value {
          if (_valueBlock) {
              _valueBlock(value);
          }
      }
      @end
      
      @implementation CocoaStream
      - (__kindof CocoaStream *)bind:(CCStreamBindBlock(^)(void))block {
          NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)];
          @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil];
      }
      @end
      
      @interface CocoaSignal ()
      @property (nonatomic, copy) void(^createBlock)(CocoaSubscriber *o);
      @end
      
      @implementation CocoaSignal
      - (CocoaSignal *)createSignal:(void(^)(CocoaSubscriber *subscriber))createBlock; {
          CocoaSignal *signal = [[CocoaSignal alloc] init];
          signal.createBlock = createBlock;
          return signal;
      }
      
      - (CCBlankBlock)subscribeValue:(CCSubscribeBlock)block {
          CocoaSubscriber *subscriber = [CocoaSubscriber subscriberWithValueBlock:block];
          if (_createBlock) {
              _createBlock(subscriber);
          }
          return subscriber.dispose;
      }
      
      - (CocoaSignal *)bind:(CCStreamBindBlock(^)(void))block {
          return [CocoaSignal createSignal:^(CocoaSubscriber *subscriber) {
              CCStreamBindBlock bindBlock = block();
              void(^tmpSubscribeBlock)(id) = ^(id a) {
                  CocoaSignal *bindReactive = (CocoaSignal *)bindBlock(a);
                  void(^tmpBindReactiveSubscribeBlock)(id) = ^(id a) {
                      [subscriber sendValue:a];
                  };
                  [bindReactive subscribeValue:tmpBindReactiveSubscribeBlock];
              };
              [self subscribeValue:tmpSubscribeBlock];
          }];
      }
      @end
      
      //sample
      CocoaSignal *aSignal = [CocoaSignal createSignal:^(CocoaSubscriber *subscriber) {
          [subscriber sendValue:@(2)];
          [subscriber sendValue:@(5)];
      }];
      
      CocoaSignal *cSignal = [aSignal bind:^CCStreamBindBlock{
          return ^CocoaSignal *(id v){
              CocoaSignal *bSignal = [CocoaSignal createSignal:^(CocoaSubscriber *subscriber) {
                  int newValue =  [v intValue] + 5;
                  [subscriber sendValue:@(newValue)];
              }];
              return bSignal;
          };
      }];
      
      [cSignal subscribeValue:^(id value) {
          NSLog(@"cSignal new value %@",value);
      }];
      /*output
        cSignal new value 7
        cSignal new value 10
      */
      

      没错,这是微调,我感觉有点ReactiveCocoa的模样了,你们觉得呢?

    • 总结

      如果通遍下来要做个总结的话,我觉得的最最最重要的是,思想的转变.RAC是构建事务关系的工具,不要用命令式那种关注过程的思想去思考它。

    • demo
      github

    • 题外话

      这个是ReactiveCocoa分享的第一节,很多细节解释在分享上讲。

    相关文章

      网友评论

          本文标题:从计算器到ReactiveCocoa

          本文链接:https://www.haomeiwen.com/subject/syidqqtx.html