本文总结了《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
3
class Signal<T, E: ErrorType> {
...
}

类型T代表的是信号发出的next event中数据的类型;E表示错误的类型必须是符合ErrorType协议的。
SwiftSignal创建的方式和Objective-CRACSignal创建方式很相似:

1
2
3
4
5
6
7
8
9
10
11
func 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>>。任何传给这个sinkevent都会被signal发射出去。
sendNext的第二个参数接收传入的值,并且构造成event传给sink

Observing Signals

观察监听一个Signal有几种不同的方式,最简单的是用observe方法:

1
2
let signal = createSignal()
signal.observe(next: { print($0) })

这段代码的输出如下:

1
tick #0
tick #1
tick #2
tick #3
tick #4

或者也可以提供一个Sink来观察SignalEvent

1
2
3
4
5
6
7
8
9
createSignal().observe(SinkOf {
event in
switch event {
case let .Next(data):
print(data.unbox)
default:
break
}
})

Event类型是一个枚举,其中的NextError事件类型包含了其相关值。上面例子中的SinkOf构造函数构造了一个类型是SinkOf<Event<String, NoError>>sink
由于Swift语言本身的限制,Event中封装的data是用LlamaKit Box Class封装起来的,需要用box/unbox来处理一下。(注:Swift 2.0之后这个限制其实已经没有了)。

Transforming Signals

SwiftSignalObjective-C的API另外一个显著的不同点就是对SignalTransforming,以map操作举例,map函数被实现为一个free function,而不是Signal的成员函数:

1
2
3
4
class Signal<T, E: ErrorType> {
...
}
func map(signal: Signal, transform: ...) -> Signal

然而如果用free functionSignalTransforming,代码就不再是『流式』的了:

1
let transformedSignal = filter(map(signal, { ... }), { ... })

所以ReactiveCocoa提供了pipe-forward运算符(从F#借鉴过来的idea)。
Swiftmap实际上是一个curred function

1
2
3
4
public 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
4
public func |> <T, E, X>(signal: Signal<T, E>,
transform: Signal<T, E> -> X)
-> X {

return transform(signal)
}

pip-forward运算符,我们可以把transforming写成这样:

1
2
3
signal
|> map { $0.uppercaseString }
|> observe(next: { print($0) })

free functions比起成员函数有很多好处,其中之一就是不受继承关系的限制。例如SwiftFoundation定义了map函数,可以运用在任何实现了CollectionType协议的类或者结构体上,而不需要有任何继承关系。

Cold and Hot Signals

在之前版本的ReactiveCocoa中,cold signalhot signal两个概念都是用RACSignal来表示的,也经常会造成一些困惑。ReactiveCocoa 3.0通过显式的定义两种不同类型(SignalSignalProducer)把二者区分开。为了理解SignalSignalProducer之间的区别,我们用个实例比较一下。

Signals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func createSignal() -> Signal<String, NoError> {
var count = 0
return Signal {
sink in
print("Creating the timer signal")
NSTimer.schedule(repeatInterval: 0.1) {
timer in
print("Emitting a next event")
sendNext(sink, "tick #\(count++)")
}
}
}

let signal = createSignal()

现在我们即使没有为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
14
func 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
4
signalProducer
|> start(next: {
print($0)
})

在上面的例子中,对于Signal,即使添加了多个observertimer也只会触发一次;对于SignalProducer,每次start都会创建一个新的timer
通常我们会用SignalProducer表示一个操作或者任务,start一个SignalProducer会启动这个操作;而Signal用来表示事件流,无论是否有Observer监听。SignalProducer非常适合于网络请求,而Signal则非常适合表示UI的事件流。

Signal producer Operations

Signal一样,SignalProducer的操作也被定义成curried free function
例如用来在pipline中插入副作用的on操作(为了可读性省略了一些函数参数):

1
2
3
4
func on<T, E>(started: (() -> ())? = nil, ...)
(producer: SignalProducer<T, E>) -> SignalProducer<T, E> {
...
}

pipe-forward运算符也为SignalProducer进行了重载:

1
2
3
4
public 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
7
signalProducer
|> on(started: {
print("Signal has started")
})
|> start(next: {
print("Next received: \($0)")
})

Applying Signal Operations to Signal Producers

SignalProducer通过lift方法来复用Signal上的操作,例如mapfilter等。lift会将Signal的操作应用在SignalProducer通过start创建的信号上。

1
2
3
4
5
6
7
8
9
10
11
12
let 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
4
public 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
8
signalProducer
|> on(started: {
print("Signal has started")
})
|> map { count($0) }
|> start(next: {
print("Next received: \($0)")
})