前面我們學過如何創(chuàng)建信號并觀察它蠢络,以及通過分析源碼了解了底層現(xiàn)實原理昼激,那這篇文章總結(jié)下SignalProducer的用法及原理。
SignalProducer封裝了延遲的和可重復的任務宴胧,這些任務會在啟動時生成信號。
那它怎么用呢?
還是看個例子吧:
每隔5秒打印一條時間信息肢藐。
第一步,創(chuàng)建SignalProducer
//Creating a SignalProducer
let signalProducer: SignalProducer<Int, NoError> =
SignalProducer { (observer, lifetime) in
for i in 0..<10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0 * Double(i))
{
observer.send(value: i)
if i == 9 { //Mark completion on 9th iteration
observer.sendCompleted()
}
}
}
}
在這里吱韭,使用一個閉包初始化SignalProducer吆豹,該閉包會在SignalProducer調(diào)用start方法時執(zhí)行。此閉包有兩個接收值:
- observer用來發(fā)送值理盆。
- lifetime為我們提供了一個機會痘煤,如果停止觀察,我們可以取消正在進行的工作猿规。
第二步衷快,創(chuàng)建觀察者
//Creating an observer
let signalObserver = Signal<Int, NoError>.Observer (
value: { value in
print("Time elapsed = \(value)")
}, completed: {
print("completed")
}, interrupted: {
print("interrupted")
})
第三步,啟動觀察姨俩,執(zhí)行block
//Start a SignalProducer
let disposable = signalProducer.start(signalObserver)
第四步蘸拔,假設我們想在10秒后中斷SignalProducer:
//Dispose after 10 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
disposable.dispose()
}
但是,根據(jù)我們當前的實現(xiàn)环葵,即使觀察者在10秒后被釋放调窍,SignalProducer仍然會在50秒內(nèi)繼續(xù)發(fā)出整數(shù)值,此時lifetime就可以利用起來了张遭。
let signalProducer: SignalProducer<Int, NoError> =
SignalProducer { (observer, lifetime) in
for i in 0..<10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0 * Double(i))
{
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
observer.send(value: i)
if i == 9 {
observer.sendCompleted()
}
}
}
}
通過判斷l(xiāng)ifetime的hasEnded屬性值邓萨,如果為true,就發(fā)送一個中斷interruption事件帝璧,SignalProducer就會終止先誉。
Signal vs SignalProducer
為了理解Signal和SignalProducer之間的區(qū)別,可以以直播跟錄播為例來類比的烁。
Signal就像直播褐耳,它是一個連續(xù)的視頻和音頻流。在給定的時間點渴庆,每個觀察者都看到相同的幀序列铃芦。觀察者不能影響整個播放過程雅镊。
而SignalProducer就像錄播。不同的觀察者刃滓,在給定的時間點仁烹,可以看到不相同的幀序列。
因此咧虎,Signal通常用于表示已經(jīng)“在進行中”的事件流卓缰,例如通知、用戶輸入等砰诵。
SignalProducer用于表示需要啟動的操作或任務征唬。例如網(wǎng)絡請求。
SignalProducer是ReactiveSwift中冷信號的實現(xiàn)茁彭,冷信號需要一個喚醒操作总寒,然后才能發(fā)送事件,而這個喚醒操作就是訂閱它理肺,因為訂閱后才發(fā)送事件摄闸。
熱信號Signal相當于現(xiàn)場直播,晚來了前面的節(jié)目就沒法看妹萨,冷信號相當于錄播年枕,早晚都能看。
SignalProducer是如何保存Observer對象的呢眠副?
跟Signal 一樣其內(nèi)部有個SignalProducerCore画切,我們主要來分析SignalProducerCore源碼:
//SignalProducerCore
internal class SignalProducerCore {
struct Instance {
let signal: Signal
let observerDidSetup: () -> Void
let interruptHandle: Disposable
}
func makeInstance() -> Instance {
fatalError()
}
func start(_ generator: (_ upstreamInterruptHandle: Disposable) -> Signal.Observer) -> Disposable {
fatalError()
}
......
}
SignalProducerCore 內(nèi)部有個Instance竣稽,它的作用是:
- 持有一個熱信號Signal囱怕,用于保存訂閱者添加的Observer對象
- 持有一個() -> Void閉包,用于執(zhí)行回調(diào)(對子類SignalCore來說 這個閉包的作用則是向上面的Signal.core.state.Observes數(shù)組發(fā)送Event)
還有兩個抽象方法毫别,供其子類去具體實現(xiàn)娃弓。SignalProducerCore有三個子類SignalCore
,GeneratorCore
和TransformerCore
岛宦,其中SignalCore
和GeneratorCore
用于普通操作台丛,而TransformerCore
則是在map, take, filterMap...等操作才會用上。
來看看SignalCore的源碼:
//SignalCore
private final class SignalCore: SignalProducerCore {
private let _make: () -> Instance
//這個action會由SignalProducer傳入
init(_ action: @escaping () -> Instance) {
self._make = action //初始化就是給閉包make賦值
}
//外部執(zhí)行SignalProducer的start函數(shù)內(nèi)部實現(xiàn)
override func start(_ generator: (Disposable) -> Signal.Observer) -> Disposable {
let instance = makeInstance()// 1. 創(chuàng)建一個熱信號signal
instance.signal.observe(generator(instance.interruptHandle)) 2. 通過參數(shù)generator創(chuàng)建一個觀察者并訂閱上面創(chuàng)建的signal
instance.observerDidSetup()3. 訂閱signal完成砾肺,執(zhí)行回調(diào)
return instance.interruptHandle
}
override func makeInstance() -> Instance {
return _make()
}
}
可以看出通過start函數(shù)挽霉,內(nèi)部創(chuàng)建了一個Signal,并且創(chuàng)建了一個觀察者訂閱了Signal变汪。
Observer中封裝的邏輯是如何被執(zhí)行的:
//SignalProducer.swift
public init(_ startHandler: @escaping (Signal.Observer, Lifetime) -> Void) {
self.init(SignalCore { //action閉包
let disposable = CompositeDisposable()
let (signal, innerObserver) = Signal.pipe(disposable: disposable)
let observerDidSetup = {
startHandler(innerObserver, Lifetime(disposable))
}
let interruptHandle = AnyDisposable(observer.sendInterrupted)
return SignalProducerCore .Instance(signal: signal,
observerDidSetup: observerDidSetup,
interruptHandle: interruptHandle)
})
}
可以看出侠坎,創(chuàng)建Producer時,內(nèi)部初始化一個SignalProducerCore裙盾,創(chuàng)建的signal实胸、observerDidSetup他嫡、interruptHandle也給了SignalProducerCore的instance。
observerDidSetup內(nèi)部封裝了startHandler的執(zhí)行庐完,創(chuàng)建的innerObserver傳入到startHandler钢属。
而在上面SignalCore的start函數(shù)內(nèi)部實現(xiàn)中,instance調(diào)用了observerDidSetup门躯,也就執(zhí)行了startHandler閉包淆党,所以在startHandler中如果我們用Observer發(fā)送值實際上是用傳入到startHandler中的innerObserver來發(fā)送值。
另外讶凉,在外界調(diào)用start函數(shù)時宁否,會創(chuàng)建一個觀察者outObserver并訂閱instance的signal。
所以每調(diào)用一次start缀遍,startHandler就會執(zhí)行一次慕匠。
舉個栗子,熟悉下用法
- 創(chuàng)建SignalProducer
let producer = SignalProducer<Int, NoError> { (innerObserver, lifetime) in
lifetime.observeEnded({
print("free sth")
})
innerObserver.send(value: 1)
innerObserver.send(value: 2)
innerObserver.sendCompleted()
}
- 創(chuàng)建一個觀察者封裝事件處理邏輯
let outerObserver = Signal<Int, NoError>.Observer(value: { (value) in
print("did received value: \(value)")
})
- 添加觀察者到SignalProducer
producer.start(outerObserver)
最終輸出:
did received value: 1
did received value: 2
free sth
當然使用SignalProducer.startXXX可以省去第二步域醇,還可以如下寫法:
typealias Producer<T> = SignalProducer<T, NoError>
let producer = Producer<Int> { (innerObserver, _) in
innerObserver.send(value: 1)
innerObserver.send(value: 2)
innerObserver.sendCompleted()
}
producer.startWithValues { (value) in
print("did received value: \(value)")
}
producer.startWithFailed(action: )
producer.startWithResult(action: )
producer.startWithXXX...
使用SignalProducer發(fā)起網(wǎng)絡請求的坑
第一步台谊,網(wǎng)絡請求方法:
func fetchData(completionHandler: (Int, Error?) -> ()) {
print("發(fā)起網(wǎng)絡請求")
completionHandler(1, nil)
}
第二步,將網(wǎng)絡請求封裝到SignalProducer中
let producer = Producer {[unowned self] (innerObserver, _) in
self.fetchData(completionHandler: { (data, error) in
innerObserver.send(value: data)
innerObserver.sendCompleted()
})
}
第三步譬挚,觸發(fā)網(wǎng)絡請求
producer.startWithValues { (value) in
print("did received value: (value)")
}
producer.startWithValues { (value) in
print("did received value: (value)")
}
咻...請看最終輸出:
發(fā)起網(wǎng)絡請求
did received value: 1
發(fā)起網(wǎng)絡請求
did received value: 1
看到了嗎锅铅,發(fā)生兩次網(wǎng)絡請求,我們當然只是想發(fā)送一次請求减宣,在訂閱時多次操作請求到的數(shù)據(jù)而已盐须,而SignalProducer會在每次被訂閱就會執(zhí)行一次初始化時保存的閉包,所以會發(fā)生多次網(wǎng)絡請求漆腌。
為了解決這個問題贼邓,可以使用Signal:
typealias NSignal<T> = Signal<T, NoError>
let signalTuple = NSignal.pipe()
signalTuple.output.observeValues { (value) in
print("did received value: (value)")
}
signalTuple.output.observeValues { (value) in
print("did received value: (value)")
}
self.fetchData { (data, error) in
signalTuple.input.send(value: data)
signalTuple.input.sendCompleted()
}
輸出: 發(fā)起網(wǎng)絡請求
did received value: 1
did received value: 1
最后,自定義Error時需要注意:
struct APIError: Swift.Error {
let code: Int
var reason = ""
}
由于默認的SignalProducer是沒有startWithValues函數(shù)的闷尿,ReactiveSwift會在Extension里給它加上startWithValues函數(shù)塑径,但是這只對NoError有效,所以在自定義Error時填具,要加上以下代碼才有效:
extension SignalProducer where Error == APIError {
@discardableResult
func startWithValues(_ action: @escaping (Value) -> Void) -> Disposable {
return start(Signal.Observer(value: action))
}
}