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