本文总结了《A FIRST LOOK AT REACTIVECOCOA 3.0》和《REACTIVECOCOA 3.0 - SIGNAL PRODUCERS AND API CLARITY》中关于ReactiveCocoa 3.0
新的Swift API的基本用法。
ReactiveCocoa 3.0
的Swift API引入了泛型、pipe-forward
运算符,并且还运用了curried functions
。新的语言特性让ReactiveCocoa
变的更好更简洁了。
Creating Signals
和Objective-C
版本ReactiveCocoa
中的RACSignal
对应的是Swift
版本中的Signal
。关于Signal
很重要的一点是这个类是个泛型类:1
2
3class Signal<T, E: ErrorType> {
...
}
类型T
代表的是信号发出的next
event
中数据的类型;E
表示错误的类型必须是符合ErrorType
协议的。Swift
的Signal
创建的方式和Objective-C
的RACSignal
创建方式很相似:1
2
3
4
5
6
7
8
9
10
11func createSignal() -> Signal<String, NoError> {
var count = 0
return Signal {
sink in
NSTimer.schedule(repeatInterval: 1.0) {
timer in
sendNext(sink, "tick #\(count++)")
}
return nil
}
}
Signal
的构造函数接受一个generator
闭包的参数,这个generator
被调用并且传入一个sink
,在这个例子里sink
的类型是SinkOf<Event<String, NoError>>
。任何传给这个sink
的event
都会被signal
发射出去。sendNext
的第二个参数接收传入的值,并且构造成event
传给sink
。
Observing Signals
观察监听一个Signal
有几种不同的方式,最简单的是用observe
方法:1
2let signal = createSignal()
signal.observe(next: { print($0) })
这段代码的输出如下:1
tick #0
tick #1
tick #2
tick #3
tick #4
或者也可以提供一个Sink
来观察Signal
的Event
:1
2
3
4
5
6
7
8
9createSignal().observe(SinkOf {
event in
switch event {
case let .Next(data):
print(data.unbox)
default:
break
}
})
Event
类型是一个枚举,其中的Next
和Error
事件类型包含了其相关值。上面例子中的SinkOf
构造函数构造了一个类型是SinkOf<Event<String, NoError>>
的sink
。
由于Swift
语言本身的限制,Event
中封装的data
是用LlamaKit Box Class
封装起来的,需要用box/unbox
来处理一下。(注:Swift 2.0
之后这个限制其实已经没有了)。
Transforming Signals
Swift
的Signal
和Objective-C
的API另外一个显著的不同点就是对Signal
的Transforming
,以map
操作举例,map
函数被实现为一个free function
,而不是Signal
的成员函数:1
2
3
4class Signal<T, E: ErrorType> {
...
}
func map(signal: Signal, transform: ...) -> Signal
然而如果用free function
做Signal
的Transforming
,代码就不再是『流式』的了:1
let transformedSignal = filter(map(signal, { ... }), { ... })
所以ReactiveCocoa
提供了pipe-forward
运算符(从F#
借鉴过来的idea)。Swift
的map
实际上是一个curred function
:1
2
3
4public func map<T, U, E>(transform: T -> U)
(signal: Signal<T, E>) -> Signal<U, E> {
...
}
也就是说,这个方法第一次调用的时候,我们先提供一个transform
,从而得到一个新的函数,可以用给定的transform
把一个signal
映射为一个新的signal
。pipe-forward
运算符允许我们把transform
串联起来:1
2
3
4public func |> <T, E, X>(signal: Signal<T, E>,
transform: Signal<T, E> -> X) -> X {
return transform(signal)
}
用pip-forward
运算符,我们可以把transforming
写成这样:1
2
3signal
|> map { $0.uppercaseString }
|> observe(next: { print($0) })
free functions
比起成员函数有很多好处,其中之一就是不受继承关系的限制。例如Swift
的Foundation
定义了map
函数,可以运用在任何实现了CollectionType
协议的类或者结构体上,而不需要有任何继承关系。
Cold and Hot Signals
在之前版本的ReactiveCocoa
中,cold signal
和hot signal
两个概念都是用RACSignal
来表示的,也经常会造成一些困惑。ReactiveCocoa 3.0
通过显式的定义两种不同类型(Signal
和SignalProducer
)把二者区分开。为了理解Signal
和SignalProducer
之间的区别,我们用个实例比较一下。
Signals
1 | func createSignal() -> Signal<String, NoError> { |
现在我们即使没有为signal
增加observer
,它依然在创建和发射event
:1
Creating the timer signal
Emitting a next event
Emitting a next event
Emitting a next event
...
Signal Producer
SignalProducer
的初始化结构和Signal
很类似:1
2
3
4
5
6
7
8
9
10
11
12
13
14func createSignalProducer() -> SignalProducer<String, NoError> {
var count = 0
return SignalProducer {
sink, disposable in
print("Creating the timer signal producer")
NSTimer.schedule(repeatInterval: 0.1) {
timer in
print("Emitting a next event")
sendNext(sink, "tick #\(count++)")
}
}
}
let signalProducer = createSignalProducer()
我们创建了一个SignalProducer
但是没有任何的observer
,会发现控制台丽没有任何输出,timer
也没有被触发。SignalProducer
可以看做是个工厂,可以通过调用SignalProducer
的成员方法start
,或者通过pipe-forward
运算符调用start
这个free function
创建一个Signal
:1
2
3
4signalProducer
|> start(next: {
print($0)
})
在上面的例子中,对于Signal
,即使添加了多个observer
,timer
也只会触发一次;对于SignalProducer
,每次start
都会创建一个新的timer
。
通常我们会用SignalProducer
表示一个操作或者任务,start
一个SignalProducer
会启动这个操作;而Signal
用来表示事件流,无论是否有Observer
监听。SignalProducer
非常适合于网络请求,而Signal
则非常适合表示UI的事件流。
Signal producer Operations
和Signal
一样,SignalProducer
的操作也被定义成curried free function
。
例如用来在pipline中插入副作用的on
操作(为了可读性省略了一些函数参数):1
2
3
4func on<T, E>(started: (() -> ())? = nil, ...)
(producer: SignalProducer<T, E>) -> SignalProducer<T, E> {
...
}
pipe-forward
运算符也为SignalProducer
进行了重载:1
2
3
4public func |> <T, E, X>(producer: SignalProducer<T, E>,
transform: SignalProducer<T, E> -> X) -> X {
return transform(producer)
}
所以,我们可以像这样为SignalProducer
创建pipeline:1
2
3
4
5
6
7signalProducer
|> on(started: {
print("Signal has started")
})
|> start(next: {
print("Next received: \($0)")
})
Applying Signal Operations to Signal Producers
SignalProducer
通过lift
方法来复用Signal
上的操作,例如map
、filter
等。lift
会将Signal
的操作应用在SignalProducer
通过start
创建的信号上。1
2
3
4
5
6
7
8
9
10
11
12let signalProducer = createSignalProducer()
let mapping: Signal<String, NoError> -> Signal<Int, NoError> = map({
string in
return count(string)
})
let mappedProducer = signalProducer.lift(mapping)
mappedProducer
|> start(next: {
print("Next received: \($0)")
})
pipe-forward
运算符也为SignalProducer
做了重载:1
2
3
4public func |><T, E, U, F>(producer: SignalProducer<T, E>,
transform: Signal<T, E> -> Signal<U, F>) -> SignalProducer<U, F> {
return producer.lift(transform)
}
因此,Signal
的操作都可以直接通过pipe-forward
应用在SignalProducer
上:1
2
3
4
5
6
7
8signalProducer
|> on(started: {
print("Signal has started")
})
|> map { count($0) }
|> start(next: {
print("Next received: \($0)")
})