ReactiveSwift框架分析3 — SignalProducer用法及源碼分析

前面我們學過如何創(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í)行。此閉包有兩個接收值:

  1. observer用來發(fā)送值理盆。
  2. 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竣稽,它的作用是:

  1. 持有一個熱信號Signal囱怕,用于保存訂閱者添加的Observer對象
  2. 持有一個() -> Void閉包,用于執(zhí)行回調(diào)(對子類SignalCore來說 這個閉包的作用則是向上面的Signal.core.state.Observes數(shù)組發(fā)送Event)

還有兩個抽象方法毫别,供其子類去具體實現(xiàn)娃弓。SignalProducerCore有三個子類SignalCoreGeneratorCoreTransformerCore岛宦,其中SignalCoreGeneratorCore用于普通操作台丛,而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í)行一次慕匠。

舉個栗子,熟悉下用法

  1. 創(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()
}
  1. 創(chuàng)建一個觀察者封裝事件處理邏輯
let outerObserver = Signal<Int, NoError>.Observer(value: { (value) in
    print("did received value: \(value)")
})
  1. 添加觀察者到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))
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末统舀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劳景,更是在濱河造成了極大的恐慌誉简,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盟广,死亡現(xiàn)場離奇詭異闷串,居然都是意外死亡,警方通過查閱死者的電腦和手機衡蚂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門窿克,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人具被,你說我怎么就攤上這事只损≡颈梗” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長携冤。 經(jīng)常有香客問我闲勺,道長菜循,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮臭杰,結(jié)果婚禮上谚中,老公的妹妹穿的比我還像新娘寥枝。我一直安慰自己,他們只是感情好囊拜,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布冠跷。 她就那樣靜靜地躺著身诺,像睡著了一般抄囚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穴亏,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音谬哀,去河邊找鬼史煎。 笑死劲室,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的充蓝。 我是一名探鬼主播谓苟,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼涝焙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孕暇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤妖滔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后座舍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沮翔,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡曲秉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年采蚀,在試婚紗的時候發(fā)現(xiàn)自己被綠了疲牵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡榆鼠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出璧眠,到底是詐尸還是另有隱情缩焦,我是刑警寧澤袁滥,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布腰鬼,位于F島的核電站嵌赠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炊豪,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拧篮。 院中可真熱鬧词渤,春花似錦、人聲如沸串绩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礁凡。三九已至高氮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間把篓,已是汗流浹背纫溃。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留韧掩,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓窖铡,卻偏偏與公主長得像疗锐,于是被迫代替她去往敵國和親坊谁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容