从计算器到ReactiveCocoa

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分享的第一节,很多细节解释在分享上讲。

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

推荐阅读更多精彩内容