惯例废话
学习了一段时间的RAC后, 对有些概念开始慢慢清晰, 然后再次看了这篇文档, 发现的确是高屋建瓴地指出了很多基本的概念, 这篇笔记我将以翻译为主, 注解为辅来写, 同时会尽我所能来解释一些名词.
文末对这篇文档的主要内容做了一次简单的概括, 嫌字多可以自己跳到最后查看
(o(╯□╰)o, 简书貌似不支持页面内标签跳转, 关于附录的东西,各位看官要手动拖到最下面了)
原文中的一些名词我打算保留, 避免在描述和类名之间对应加上一道鸿沟, 下面是一些参考释意:
名词 | 释意 |
---|---|
Stream | 流 |
Monad | 单子 |
Signal | 信号 |
Subscribe(r) | 订阅(者) |
Dispose | 解除 |
Completed | 完成 |
Subject | 主题 |
Command | 指令 |
Connection | 连接 |
scheduler | 调度器 |
框架总览
这篇文档包含了对ReactiveCocoa(译注:后文简称RAC)框架中各个组件的简要叙述, 以及对这些组件各自的职责和如何协同工作进行解释. 也就是说, 这篇文档是学习新模块的起点, 从这里去找对应的文档来看.
例如, 关于RAC的例子和帮助文档, 查看README或者设计指导 (译注: 这篇文档也很有帮助, 值得一看)
Streams
Stream, 也就是RACStream抽象类, 是一系列对象的值.
值可能即可可用, 也可能将来可用(译注: 例如懒加载), 但是必须按序列来取. 没有办法绕过计算第一个值而直接获取后续的值.
Stream是Monads(译注: 关于Monad我在附录中有解释, 看看先). 相比于其它非Monad的东西, Monad可以通过几个简单基本的操作来构建复杂的操作. RACStream的实现类似于Haskell的Monoid和MonadZip.
RACStream本身并没有什么用, 大多数stream都被当做signals和sequences了.
Signals
signal, 也就是RACSignal类, 是一个push驱动的stream(译注: 所谓push驱动和pull驱动是数据源的角度来看的, 对应为主动和被动, push是数据源会把数据主动推出去, pull则是要自己去拉取才会拿到数据).
Signals通常代表将来会被取出的数据. 当代码执行完毕或数据已接收, 信号的值已被发送给subscriber. 用户必须订阅一个signal才能访问它的值.
signals发送给subcriber三种不同类型的事件:
- next事件提供了一个stream的新值. RACStream的方法只操作这种事件类型. 与Cocoa的集合不同的是, 信号包含nil是允许的.
- error事件意味着在信号结束之前有错误发生了. 这个事件可能包含了一个
NSError
对象来指示错误. 错误必须被特殊处理掉, 它们也不会包含在stream的值中. - completed事件意味着信号已经成功结束, 并且没有更多的值会被加入到stream中了. completed事件必须要被特殊处理, 它不会被stream包含.
一个信号的生命周期中包含了一系列的next
事件, 最终伴随一个error
或者completed
事件而结束(不会2者同时出现).
Subscription
一个Subscriber可以是任何东西, 它等待着信号的事件到来. 在RAC中, subscriber是任意实现了RACSubscriber协议的对象.
通过调用-subscribeNext:error:completed:方法或其他便利方法来创建一个subscription. 技术上来说, 大多数RACStream和RACSignal操作也会创建subscription, 通常是一些实现细节的中间态subscription.
Subscription会持有信号, 但是会在signal发送completed或者error事件后自动dispose.(译注: RAC内存管理第一条--必须保证signal以completed或error事件收尾). subscription也可以手动dispose
Subjects
subject, 也就是RACSubject类, 是一个可以手动控制的Signal.(译注: 也就是实现了RACSubscription协议)
Subject可以被看作是"可变"的signal, 就像NSMutableArray
之于NSArray
. Subject在把非RAC代码代入到Signal的世界中起到了非常重要的桥接作用.
例如, 可以取代在block回调中处理应用逻辑, 这些block可以简单地发送事件来共享subject. subject可以作为RACSignal返回, 隐藏回调的实现细节.
一些Subject还提供了额外的操作. 比如RACReplaySubject可以用来缓存事件, 分发给以后的subscriber, 就像一个网络请求已经结束了, 但是其它处理这个结果的事情还未就绪.
(译注: Subject相比较Signal有三个需要注意的点, 一是Subject可以自己发送事件, 而Signal不行; 二是Subject发送的值是共享的, 无论多少subcriber收到的都是同一份, 而Signal不是; 三是Subject是热信号(Hot Signal)而Signal是冷信号(Cold Signal). 关于冷热信号在附录也有简单阐述, 看看先)
Commands
Command, 也就是RACCommand类, 创建和订阅一个Signal来响应一些操作. 这使用户在APP上的交互当作副作用(side-effecting)(译注: side-effecting在学习RAC中会出现多次, 总体来说就是用户对状态的修改, 因为RAC的本意就是封装了所有状态的修改, 因此, 如果用户自己在某些代码中要自己管理某些状态, 就会导致和一般字面上的意义不一样, 就是副作用的代码, 具体可以看看学习笔记四中使副作用显性化的描述.)来执行变得更加容易了.
通常一个触发指令的作用是UI驱动的, 例如一个按钮被点击了. Command也可以通过信号来自动disable掉, 并且这个disable的状态还可以通过UI中disable相关联的控件来体现.
在OS X上, RAC添加了一个rac_command
属性到NSButton
来自动设置这些行为.
Connections
connection, 也就是RACMulticastConnection类, 是一次多个subscriber之间共享的订阅.
Signal默认是cold, 意思是每一次添加了新的subscriber, 都会开始执行任务. 这个行为通常是值得的, 因为这意味着对每个subscriber来说, 数据每次都会被重新计算, 但是这样的行为在Signal有副作用或任务庞大的时候也会出问题(例如, 发送一个网络请求).
connection通过RACSignal的-publish
或-multicast
方法来创建, 并且保证无论有这个connection被订阅了多少次, 都只有一个潜在的订阅被创建. 一旦连接上, connection就称被为hot, 并且潜在的订阅会一直保持活跃, 直到所有的的connection的订阅都已经dispose.
Sequences
sequences, 也就是RACSequence类, 是一个pull驱动的stream.
Sequence算是一种集合类, 类似于NSArray
. 不过与数组不同的是, sequence里面的值默认是懒计算的(只有当它们被用到时才计算), 这样提高了只有一部分sequence值被访问的性能. 与Cocoa集合类一样, sequence不能包含nil
.
Sequence与Clojure的sequence或Haskell的List类似.
RAC添加了-rac_sequence
方法到大多数Cocoa的集合类中, 从而允许它们使用RACSequence来替代之.
Disposables
RACDisposable类被用来取消操作和资源清理.
Disposables最常用来解除对Signal的订阅. 当订阅被dispose, 对应的subscriber将再也不会受到Signal发送的事件了. 除此之外, 任何与此次订阅相关的任务(后台处理, 网络请求等)都会被取消掉, 因为这些结果已经不再需要了.
查看RAC的设计指导获取更多关于取消操作的信息.
Schedulers
scheduler, 也就是RACScheduler类, 是一系列的操作队列来支持Signal执行任务或分发结果.
scheduler与GCD队列类似, 而且scheduler支持去掉操作(因为Disposables), 并且总是顺序执行. 有一个例外就是+immediateScheduler, 这个scheduler不会提供同步执行. 这有助于防止死锁, 并且鼓励使用Signal操作符来取代阻塞任务.
RACScheduler与NSOperationQueue
也有一些类似, 但是scheduler不支持任务排序或或者说是一个任务依赖另外一个.
Value types
RAC提供了一些五花八门的类来方便stream中值的表示:
-
RACTuple是一个轻量, 常量大小的集合, 允许包含
nil
(会被表示为RACTupleNil
). 通常被来表示多个stream值的混合. - RACUnit是一个表示"空值"的单例. 它用来表示stream中已经没有更多有意义的值了
-
RACEvent把任意的Signal事件表示为一个单一的值. 主要用于RACSignal的
-materialize
方法.
<a name="Conclusion"></a>总结
这篇文档除了描述了一些名词的概念之外, 更重要的是告诉了我们各个类之间的细微差别:
- RACSignal是冷信号, 每次订阅都会执行一次任务
- RACSubject是热信号, 每个任务都只执行一次, 各subscriber共享结果
- RACReplaySubject相比RACSubject多了一个缓冲区, 可以回放之前的任务
- RACCommand主要用于UI相关的操作
- RACDisposable可以取消对Signal的订阅
- RACMulticastConnection可以合并多个订阅为一个, 避免任务被多次执行
- ....
附录
<a name="Monad"></a>Monad
关于Monad在Wikipedia的解释中有一句:
A monad is defined by a return operator that creates values, and a bind operator used to link the actions in the pipeline.
也就是说, 一个Monad被定义为返回一个构造值的操作符, 并且一个bind操作符用来在管道中连接多个操作.
貌似还挺绕, 其实就是说, Monad本质是一个操作符(可以理解为一个函数), 这个操作符的作用就是返回一个数值. 为什么这么麻烦, 而不直接返回这个数值呢? 后面这句话解释了原因, 就是如果直接返回数值的话, 那就无法继续连接操作了, 所以这边定义了一个bind
操作来专门连接多个操作符. 具体参考Linux命令行的管道.
<a name="ColdHotSignal"></a>冷热信号
关于冷热信号我之前在网上看到一位博主的博客关于冷热信号的解释大意如下:
Signal就像插座, subscriber就像是插头, 如果没有subscriber来订阅Signal, 那就是"没通电", 这信号就是"冷"的, 反之就是"热"的.
然后我又看到美团的技术博客上说的不一样, 主要信息摘抄图下:
1.热信号是主动的, 即使没有订阅者, 也会时刻推送...而冷信号是被动的, 只有订阅的时候才会发送信息...
2.热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息...而冷信号只能一对一,当有不同的订阅者, 消息会重新完成发送...
两位描述差距很大, 所以秉着追寻事实的心态, 我找到了官方文档里面有这么一句冷热信号对比:
Is the signal hot (already activated by the time it's returned to the caller) or cold (activated when subscribed to)
上下文是在探究RACSignal所代表的含义, 从这里我们可以看出冷热信号的一个重要区别:
热信号: 这个信号返回给调用者的时候就已经激活
冷信号: 当被订阅的时候激活
在文档中没有找到更多的对比解释, 但我觉得已经足够描述两者的区别了. 所以从这可以看出, 美团技术团队的描述是更加准确的, 当然, 关于一对多这个说法, 我暂且保留意见, 因为这不是核心区别, 冷信号也可以有多个subscriber, 只不过值不是共享的.
这也提醒我们, 很多时候官方文档还是准确的, 很多时候我们在学习的时候, 如果可以的话, 还是尽量去看原文档, 尽量减少理解误差.
还值得一提的是, RAC3.0版本对冷热信号的类使用上有一定的修改, 参考目录
网友评论