It's always hard to start, but after that, all you need is persistence.
这已经是第二次看 ReactiveCocoa 了,上次是几个月之前,虽然上次没做什么总结,但是回顾了几天后收获还是很大,这次就趁热把脑袋的还热乎的想法记录下来供以后参考。
我这里就不再去定义 RAC 是什么,需要了解基础的去看看学习 RAC 必看的几篇文章:
- Ray 家的
- 雷纯锋大大
- RAC 官网
注:我目前看的还是 ReactiveCocoa v2.5 版本。
RACStream
RACStream 是基于函数式编程中的 Monad 的概念建立的,可以说它是整个 RAC 的基石,有了它才得以实现 RAC 中的流式和链式编程。
在这个类中,最重要的就是 bind 这个函数,为了便于理解,我们可以看看 haskell 中 Monad 中的对 bind 的函数定义:
it takes a monadic value (that is, a value with a context) and feeds it to a function that takes a normal value but returns a monadic value, and return another monadic value.
翻译过来就是这个函数接收一个带有上下文的值,在它上面应用一个接收普通值但返回带有上下文的值的函数,最后返回另一个带有上下文的值。
这里我们用 Swift 来模拟这个函数的签名:
func bind(a: Monad, Int -> Monad) -> Monad
但是你会发现这和 RAC 中 bind 的签名并不相同:
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);
- (instancetype)bind:(RACStreamBindBlock (^)(void))block;
相反倒是和 flatmap 的签名很像。
那么 RAC 中的 bind 函数是干嘛的呢?里面的原理理解起来很复杂,我们只需要知道的是,在 RACStream 的 operation 这个分类中,很多例如 flattenMap、scanWithStart 等函数都是直接或者间接通过 bind 来构建,这些函数不仅能够链式调用,还把‘值流’这个概念贯穿起来。
要彻底的理解 bind 函数的作用感觉不太容易,我感觉也没有太大的必要去画太多的时间去研究它,了解函数式操作的那些函数的具体的意义和作用反而更有实用价值。
RACSignal 和 RACSubscriber
通常来说,我们并不会直接用到 RACStream,而是用它的一个重要的子类 RACSignal。
为了说清楚这一部分,我们来先看几个基本的概念:
首先是 action,这是我自己添加的一个概念,action 可以是点击按钮、访问网络或者打开文件等等动作,动作必然会产生 event,事件中包含相应的结果值,RAC 中有三类事件,分别为 next、error 和 complete,next 表示事件成功,可以继续进行下一步操作,error 表示事件失败,complete 表示事件完成。
接下来就是 signal 信号,这个概念比较抽象,官方的解释是:
Signals generally represent data that will be delivered in the future.
信号代表着将在未来传递的值,这个值就是事件包含的值,信号可以看做是‘值流’的源头。
如果你想要拿到这些值,就必须订阅信号,成为订阅者。
信号是 push-driven 的,也就是说值是一个一个的推送给订阅者,订阅者无法干预或主动获取想要的值,不仅如此,信号在传递值流的过程中,还可以产生副作用。
如果一个信号只有在每次被订阅之后才开始传递值并产生副作用,那么这样的信号称为‘冷信号’,相反,如果信号的值传递和订阅者没关系,那么这个信号就被称为‘热信号’。
创建一个信号很简单:
RACSignal *loginSigal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[self loginWithSuccess:^(id userInfo) {
[subscriber sendNext:userInfo];
[subscriber sendCompleted];
} failed:^(NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
[[[loginSigal doNext:^(id x) {
// 副作用
}] map:^id(id value) {
// 转换值
return [value description];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"%@", error);
}];
createSignal 方法接收一个叫 didSubscribe
的 block,这个 block 接收一个 subscriber 并返回一个 disposable,关于这个 disposable 我们稍后再说。
可以看到,在这个 didSubscribe block 里面,先发起来一个登陆请求,在成功的回调中,给 subscriber 发送 sendNext 消息,然后再发送 sendCompleted 消息,在失败的回调中,发送 sendError 消息。
一开始看起来是比较奇怪的,因为发送值的工作竟然是由 subscriber 做,感觉跟订阅者的定义有冲突,后来我想了一下,这应该是借鉴了 OC 发送消息的思想,这里的 sendNext 相当于是给订阅者发送 next value,而不应该看作是调用 sendNext 方法。在 RAC 中,通常来说我们并不会去实例化一个 subscriber,我们只能通过 subscribeNext 等方法来订阅信号,而信号内部会帮我们创建 subscriber 来完成相应的工作。
通过阅读源码可以发现,RACSignal 其实是通过子类 RACDynamicSignal 来完成创建信号的工作的。RACDynamicSignal 内部有一个 didSubscribe 的 block 属性,createSignal 传入的 block 的一份 copy 被赋值给这个属性保存起来。
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
接下来,当每一次我们给 signal 发送以 subscribe 开头的消息时,signal 会首先创建一个 subscriber 实例,并且把相应的 block (nextBlock、errorBlock...)赋值给 subscriber 相应的属性,
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
// 创建一个订阅者,并把 nextBlock 保存下来
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
然后调用了一个非常重要的方法:
- (RACDisposable *)subscribe (id)subscriber;
这个方法还是由 RACDynamicSignal 来实现的,抛开其他的,在这个方法里调用了最开始我们传入的那个 didSubscribe block,这时候信号才执行。
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 这里调用了之前的 didSubscribe,是作为属性保存下来的。
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
拿上面的代码举例,这时候才开始执行 loginWithSuccess,成功后执行 subscriber 的 sendNext 方法,这个方法会调用 subscribeNext 传入保存的的 nextBlock。
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
这个值在传到 nextBlock 之前我们还可以对它做不少的操作和副作用,这应该就是 RACStream 中 bind 函数的功劳了。具体如何实现的不深究了,因为实在看不懂。
总结
到这里先结束了,了解了一些基础和思想后,接下来的笔记可能更关心 RAC 中各个组成成分的用法,让我们投入到实战吧。