内容简介:此文基于ReactiveCocoa是一个将函数响应式编程范式带入Objective-C的一个开源库,由Objective-C能够得此发展源于大量其他语言的工程师加盟iOS社区,Objective-C第一次被苹果以外的人打磨。
此文基于 ReactiveObjC-3.1.0
ReactiveCocoa是一个将函数响应式编程范式带入Objective-C的一个开源库,由 Josh Abernathy 和 Justin Spahr-Summers 在对GitHub for Mac的开发过程中建立。
Objective-C能够得此发展源于大量其他语言的工程师加盟iOS社区,Objective-C第一次被苹果以外的人打磨。
本文主要介绍什么是函数响应式编程,RAC的概览,冷热信号转换的原理,基本的操作符等
函数式编程
什么是函数式编程?举例:
命令式编程
int a, b, r; void add_abs() { scanf("%d %d", &a, &b); r = abs(a) + abs(b); printf("%d", r); }
函数式编程
int add_abs(int a, int b) { return abs(a) + abs(b); }
函数式编程的几个特点
- 函数是第一公民,可以作为参数和返回值
- 函数的调用不会修改状态,不会修改全局变量,任何一次调用相同的输入都会返回相同的输出
函数式编程的几个技术
递归
递归的好处就是减少代码
高阶函数
高阶函数是参数或者返回值为函数的函数
pipeline
把函数实例成一个一个的action,然后把一组action放到一个数组中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数操作
map & reduce & filter
map是一个高阶函数,一个集合的每一个元素通过给定一个函数进行处理,然后转化为另一个集合。
例如map (toLower) "abcDEFG12!@#" 的结果就是"abcdefg12!@#"
reduce也是一个高阶函数,通过一个combiner函数,让集成中两个元素进行处理,得到结果后再与其余元素进行处理,最后得到一个返回值
例如reduce (+) 0 [1..5]最后的结果是15
filter,高阶函数,集合中每一个元素通过一个predicate函数得到true/false,最后得到一个元素处理结果为true组成的集成
例如filter (isAlpha) "$#!+abcDEF657" 结果是 "abcDEF"
柯里化(curry)
curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
// JS var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); // 3 addTen(2); // 12
响应式
响应式编程最开始是来自微软的.Net框架 Reactive Extensions
什么是响应式编程?响应式编程就是用asynchronous data streams 进行编程。比较常听到的是asynchronous event streams,比如click event,event bus等。但这边注重的是data与stream,Reactive Extensions将event延伸为data,Stream就是一个 按时间 排序 的Events(Ongoing events ordered in time)序列。
流可以发送3种不同的事物:一个值(类型不限),一个错误或者一个已完成的信号。 我们只能异步捕获这些发送的事件,即:定义一个函数用于当一个值发送出来时再执行,定义一个函数用于当错误发送出来时执行,定义一个函数用于当完成发送出来时执行。
如图
--a---b-c---d---X---|-> a, b, c, d 都是发送出的值 X 是错误 | 是 'completed' 信号 ---> 是时间线
RP本身是建立于观察者模式之上的一种编程范式。对流的 “侦听” 又称为 订阅(subscribing),而定义的函数即为 观察者(observer),流就是 主题(subject, observable)。这是一个典型的观察者模式。
Streams
ReactiveCocoa由两大主要部分组成:signals (RACSignal) 和 sequences (RACSequence)。
signal 和 sequence 都是streams,他们共享很多相同的方法。ReactiveCocoa在功能上做了语义丰富、一致性强的一致性设计:signal是push-driver的stream,sequence是pull-driver的stream。pull-driver是任何时刻我有数据了你都可以获取到,因为数据先存储了,取数据的时间控制在调用者上。push-driver是任何时刻有数据了都会push给调用者,如果你没处理就丢失了。
RACStream是一个抽象类,是不能直接实例化的
Signals
根据上面响应式编程流的概念,信号给他们的订阅者发送三种不同的事件类型:
- next事件,next从流中提供一个新的值
- error事件,该事件表示早一个信号正常结束之前发生了一个错误。
- completed事件:表示信号正常结束,同时也没有其他更多的值添加到流中。
Subjects
一个Subject,在RAC中代表的是RACSubject类,是一种可以被手动控制的信号。Subject可以认为是可变的信号,就像NSMutableArray对于NSArray一样。Subject是很有用的连接非RAC代码到RAC的很有用的工具。
Sequences
sequence,在RAC中代表的是RACSequence类,是一种pull-driven的流。Sequence是一种集合类型,类似NSArray.
RACSubscriber
RACSubscriber是订阅者,所有实现了 RACSubscriber 协议的类都可以作为信号源的订阅者。一次订阅是通过调用-subscribeNext:error:completed产生的。订阅会持有它的signal对象,并且在信号completed或者error的时候释放。
RACScheduler
调度器,是一个串行的信号执行队列,用来执行任务或者传递结果。Schedulers类似于GCD中的队列,但是scheduler支持取消队列(通过disposables),并总是串行执行的。
RACDisposable
清洁工,Disposables常常用来取消对一个信号的订阅。
Commands
command,在RAC中表示的是RACCommand类,可以创建或者订阅一个信号用来响应某些action动作。这可以很方便的来处理App中的用户交互。
基本用法如下
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { NSLog(@"执行命令"); return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"请求数据"]; [subscriber sendCompleted]; return nil; }]; }]; [command.executionSignals subscribeNext:^(id x) { NSLog(@"signal %@",x); [x subscribeNext:^(id x) { NSLog(@"value %@",x); }]; }]; [command execute:@1]; // output: 执行命令 - signal <RACDynamicSignal: 0x608000227280> - value 请求数据
首先创建一个RACCommand,signalBlock是需要返回一个信号的,可以input(execute传入的值)来返回不同signal。在执行execute的时候会调用RACCommand的_signalBlock,并且把block返回的冷信号通过connection转换为热信号,然后把热信号加入_activeExecutionSignals,这样订阅command.executionSignals(_activeExecutionSignals数组转换的高阶信号)里面收到的信号就可以获取_signalBlock冷信号的值了。
UIButton这里有一个常用的用法可以方便的接收touch事件,如下
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { NSLog(@"button was pressed!"); return [RACSignal empty]; }];
那是因为setRac_command:方法里面先addTarget:action:forControlEvents,然后action里面的实现是self.button.rac_command 调用execute:,这样touch事件发出时RACCommand的signal就能收到回调了。
Connections
在提到RACMulticastConnection的时候需要说一下冷信号和热信号的概念。
-
Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
-
Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
这里可以看 细说ReactiveCocoa的冷信号与热信号(一) 的例子
RACSignal家族中,RACSubject就是热信号,除此还有RACReplaySubject,RACBehaviorSubject,RACGroupedSignal;
RACSubject是继承自RACSignal,并且它还遵守RACSubscriber协议。这就意味着它既能订阅信号,也能发送信号。在RACSubject里面有一个NSMutableArray数组,里面装着该信号的所有订阅者。
RACSignal就是冷信号,除此还有RACEmptySignal,RACReturnSignal,RACDynamicSignal,RACErrorSignal,RACChannelTerminal。
根据RACSignal订阅和发送信号的流程,我们可以知道,每订阅一次冷信号RACSignal,就会执行一次didSubscribe的block。这个时候就是可能出现问题的地方。如果RACSignal是被用于网络请求,那么在didSubscribe block里面会被重复的请求。
如何做到信号只执行一次didSubscribe block,最重要的一点是RACSignal冷信号只能被订阅一次。由于冷信号只能一对一,那么想一对多就只能交给热信号去处理了。这时候就需要把冷信号转换成热信号。
冷信号转换成热信号需要用到RACMulticastConnection 这个类。
RACMulticastConnection最主要的是保存了两个信号,一个是暴露给外部的类型为RACSubject的signal属性,一个是内部的sourceSignal(RACSignal类型)。
用sourceSignal去发送信号,内部再用RACSubject去订阅sourceSignal,然后RACSubject会把sourceSignal的信号值依次发给它的订阅者们。
RACSignal的订阅过程
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@1]; [subscriber sendCompleted]; return nil; }]; [signal subscribeNext:^(id x) { NSLog(@"x%@",x); }];
- 首先RACSignal调用createSignal方法创建实例,这里实际是通过RACDynamicSignal子类
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { RACDynamicSignal *signal = [[self alloc] init]; signal->_didSubscribe = [didSubscribe copy]; return [signal setNameWithFormat:@"+createSignal:"]; }
实际就是给_didSubscribe变量赋值执行订阅操作需要执行的 block
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 优秀开源库SDWebImage源码浅析
- 优秀开源库SDWebImage源码浅析
- Californium开源框架之源码分析(三)
- Californium开源框架之源码分析(四)
- Android开源框架源码分析:Okhttp
- Android开源框架源码分析:Okhttp
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。