ReactiveCocoa 4 如何使用冷熱訊號

RichardXG發表於2016-12-18

ObjectiveCBridging

  • RACSignal -> Signal, SignalProducer

  • RACCommand -> Action

  • RACScheduler -> SchedulerType

  • RACDisposable -> Disposable

具體可以參看ObjectiveCBridging.swift

什麼是ReactiveCocoa

什麼是訊號

ReactiveCocoa的冷熱訊號

RAC4的 Signal 對應RAC2的 RACSubject,為熱訊號,SignalProducer 對應 RACSignal 為冷訊號。

  • 熱訊號是主動的,就算沒有訂閱者也會即刻推送;冷訊號是被動的,有訂閱者才推送

  • 熱訊號可以有多個訂閱者,一對多;冷訊號只能一對一,當有新的訂閱者,訊號是重新完整傳送的

形象的說:熱訊號像是直播,冷訊號像是點播

熱訊號

參看一個例子

// 建立訊號
let (signal, observer) = Signal<String, NSError>.pipe()
// 監聽器1:立即監聽
signal.observeResult {
    NSLog("Subscriber 1 get a next value: ($0)")
}
// 監聽器2:0.1s後監聽
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 0.1)) {
    signal.observeResult({ (result) in
        NSLog("Subscriber 2 get a next value: (result)")
    })
}
// 傳送Next訊號:1s後傳送"package 1"
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) { 
    NSLog("Signal send package 1 ...")
    observer.sendNext("package 1")
}
// 監聽器3:1.1s後監聽
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1.1)) { 
    signal.observeResult {
        NSLog("Subscriber 3 get a next value: ($0)")
    }
}
// 傳送Next訊號: 2s後傳送"package 2"
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 2)) { 
    NSLog("signal send package 2 ...")
    observer.sendNext("package 2")
}

執行:

2016-12-18 19:42:16.206588 TestRAC4[695:389286] start ...
2016-12-18 19:42:17.249072 TestRAC4[695:389286] Signal send package 1 ...
2016-12-18 19:42:17.254583 TestRAC4[695:389286] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 19:42:17.254899 TestRAC4[695:389286] Subscriber 2 get a next value: .Success(package 1)
2016-12-18 19:42:18.393983 TestRAC4[695:389286] signal send package 2 ...
2016-12-18 19:42:18.394386 TestRAC4[695:389286] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 19:42:18.394582 TestRAC4[695:389286] Subscriber 2 get a next value: .Success(package 2)
2016-12-18 19:42:18.394769 TestRAC4[695:389286] Subscriber 3 get a next value: .Success(package 2)

正如之前提到的熱訊號的特點,熱訊號不關心是否有人監聽,有產生訊號就傳送,有人監聽就發個監聽者。0.1s,2s分別傳送next事件,0s,0.1s,1.1s分別產生一個監聽者,所以第一個訊號只有監聽者1,2能收到;第二個next發出時三個監聽器都準備好了,都能收到。

冷訊號

再看看冷訊號的例子:

let producer = SignalProducer<String, NSError>.init { (observer, _) in
    QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
        NSLog("Producer send package 1 ...")
        observer.sendNext("package 1")
    }
    QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 2)) {
        NSLog("Producer send package 2 ...")
        observer.sendNext("package 2")
    }
}
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 2)) {
    NSLog("setup Subsrciber 1")
    producer.startWithResult({ (msg) in
        NSLog("Subscriber 1 get a next value: (msg)")
    })
}
QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 3)) {
    NSLog("setup Subsrciber 2")
    producer.startWithResult({ (msg) in
        NSLog("Subscriber 2 get a next value: (msg)")
    })
}

以下是輸出:

2016-12-18 20:05:36.396118 TestRAC4[703:394073] start ...
2016-12-18 20:05:38.600637 TestRAC4[703:394073] setup Subsrciber 1
2016-12-18 20:05:39.681697 TestRAC4[703:394073] setup Subsrciber 2
2016-12-18 20:05:39.682077 TestRAC4[703:394073] Producer send package 1 ...
2016-12-18 20:05:39.685559 TestRAC4[703:394073] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 20:05:40.753463 TestRAC4[703:394073] Producer send package 2 ...
2016-12-18 20:05:40.753865 TestRAC4[703:394073] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 20:05:40.754679 TestRAC4[703:394073] Producer send package 1 ...
2016-12-18 20:05:40.754898 TestRAC4[703:394073] Subscriber 2 get a next value: .Success(package 1)
2016-12-18 20:05:41.881230 TestRAC4[703:394073] Producer send package 2 ...
2016-12-18 20:05:41.881673 TestRAC4[703:394073] Subscriber 2 get a next value: .Success(package 2)

使用冷訊號,訊號在程式起來第1s,2s分別傳送了一個next。而在2s,3s分別建立了一個監聽。第一個監聽建立後1s,2s分別收到一個訊號。第二訊號建立的1s,2s後也分別收到了同樣的訊號。

冷訊號轉化為特殊熱訊號

由於冷訊號每次訂閱都重新傳送事件,故有時候我們需要多次訂閱時會造成不希望的重複傳送(這稱為訊號的副作用),我們需要將冷訊號轉為具有熱訊號特徵的冷訊號。

參看一個例子,我們用冷,熱訊號在0.1s後傳送三個next事件,然後如下進行監聽:

// 我們用2個監控來監控generateSignal()產生的訊號
let signal = generateSignal()

signal.startWithResult{ NSLog("Subscriber 1 get a next value: ($0)") }

QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
    signal.startWithResult{ NSLog("Subscriber 2 get a next value: ($0)") }
}

先看看如果是產生了普通的冷訊號:

func generateSignal() -> SignalProducer<String, NSError> {
    let producer = SignalProducer<String, NSError>.init { (observer, _) in
        QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
            NSLog("producer send package 1 ...")
            observer.sendNext("package 1")
            NSLog("producer send package 2 ...")
            observer.sendNext("package 2")
            NSLog("producer send package 3 ...")
            observer.sendNext("package 3")
        }
    }
    return producer
}

輸出如我們所料,訊號被重複傳送了。

2016-12-18 21:46:21.295670 TestRAC4[742:413995] start ...
2016-12-18 21:46:22.389634 TestRAC4[742:413995] producer send package 1 ...
2016-12-18 21:46:22.397032 TestRAC4[742:413995] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 21:46:22.397397 TestRAC4[742:413995] producer send package 2 ...
2016-12-18 21:46:22.398235 TestRAC4[742:413995] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 21:46:22.398415 TestRAC4[742:413995] producer send package 3 ...
2016-12-18 21:46:22.398608 TestRAC4[742:413995] Subscriber 1 get a next value: .Success(package 3)
2016-12-18 21:46:23.496484 TestRAC4[742:413995] producer send package 1 ...
2016-12-18 21:46:23.496886 TestRAC4[742:413995] Subscriber 2 get a next value: .Success(package 1)
2016-12-18 21:46:23.497061 TestRAC4[742:413995] producer send package 2 ...
2016-12-18 21:46:23.497255 TestRAC4[742:413995] Subscriber 2 get a next value: .Success(package 2)
2016-12-18 21:46:23.497413 TestRAC4[742:413995] producer send package 3 ...
2016-12-18 21:46:23.498869 TestRAC4[742:413995] Subscriber 2 get a next value: .Success(package 3)

如果改成熱訊號呢?

func generateSignal() -> Signal<String, NSError> {
    let (signal, observer) = Signal<String, NSError>.pipe()
    QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 0.1)) {
        NSLog("producer send package 1 ...")
        observer.sendNext("package 1")
        NSLog("producer send package 2 ...")
        observer.sendNext("package 2")
        NSLog("producer send package 3 ...")
        observer.sendNext("package 3")
    }
    return signal
}

let signal = generateSignal()

signal.observeResult { NSLog("Subscriber 1 get a next value: ($0)") }

QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
    signal.observeResult{ NSLog("Subscriber 2 get a next value: ($0)") }
}

輸出也如我們所料,只有監聽2能搜到事件,且訊號都只傳送一次。

2016-12-18 21:54:34.527911 TestRAC4[745:415500] start ...
2016-12-18 21:54:34.634711 TestRAC4[745:415500] producer send package 1 ...
2016-12-18 21:54:34.637899 TestRAC4[745:415500] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 21:54:34.638067 TestRAC4[745:415500] producer send package 2 ...
2016-12-18 21:54:34.638146 TestRAC4[745:415500] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 21:54:34.638195 TestRAC4[745:415500] producer send package 3 ...
2016-12-18 21:54:34.638334 TestRAC4[745:415500] Subscriber 1 get a next value: .Success(package 3)

緩衝 buffer

通過SignalProducer.buffer()建立,是一個事件的佇列(通常指定數量),當新訊號產生時,會重新執行佇列裡的事件。和pipe相似,這個方法返回一個觀察者。每個發給這個觀察者的事件會被加入佇列。如果這個緩衝區已經達到建立時預定的數量,當新的事件發來時,最早的一個會被移出佇列。

如果使用buffer來處理呢?我們將上面的冷訊號稍作修改:

func generateSignal() -> SignalProducer<String, NSError> {
    let (producer, observer) = SignalProducer<String, NSError>.buffer(Int.max)
    QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
        NSLog("producer send package 1 ...")
        observer.sendNext("package 1")
        NSLog("producer send package 2 ...")
        observer.sendNext("package 2")
        NSLog("producer send package 3 ...")
        observer.sendNext("package 3")
    }
    return producer
}

輸出發現,似乎冷訊號變成了熱訊號了,只傳送了一次。但又具有冷訊號的特點:兩個監聽器都能收到事件。

2016-12-18 21:44:29.971493 TestRAC4[740:413482] start ...
2016-12-18 21:44:31.071352 TestRAC4[740:413482] producer send package 1 ...
2016-12-18 21:44:31.075839 TestRAC4[740:413482] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 21:44:31.076115 TestRAC4[740:413482] producer send package 2 ...
2016-12-18 21:44:31.076580 TestRAC4[740:413482] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 21:44:31.076773 TestRAC4[740:413482] producer send package 3 ...
2016-12-18 21:44:31.077035 TestRAC4[740:413482] Subscriber 1 get a next value: .Success(package 3)
2016-12-18 21:44:31.078167 TestRAC4[740:413482] Subscriber 2 get a next value: .Success(package 1)
2016-12-18 21:44:31.078420 TestRAC4[740:413482] Subscriber 2 get a next value: .Success(package 2)
2016-12-18 21:44:31.078771 TestRAC4[740:413482] Subscriber 2 get a next value: .Success(package 3)

buffer(Int.max)這裡的參數列示這個緩衝大小,如果設定為1,那麼第二個next發出時,快取裡第一個就被替換成了第二個next,第三個來時同理。對於第一個監聽者它能收到全部事件;對於第二個監聽者只能收到快取裡的事件也就是最後的package 3。如果這第一個監聽者在發出訊號之後再開始監聽,那麼它也將和第二個監聽者一樣只能收到緩衝裡的事件。

replayLazily

假如我們不能修generateSignal()裡的程式碼,這是由第三方提供的,我們可以使用replayLazily。

let producer = generateSignal().replayLazily(Int.max)  // 直接用之前的冷訊號

producer.startWithResult { NSLog("Subscriber 1 get a next value: ($0)") }

QueueScheduler.mainQueueScheduler.scheduleAfter(NSDate(timeIntervalSinceNow: 1)) {
    producer.startWithResult { NSLog("subscriber 2 get a next value: ($0)") }
}

輸出如下,和buffer是一樣的效果:

2016-12-18 22:04:48.364076 TestRAC4[753:417953] start ...
2016-12-18 22:04:48.482124 TestRAC4[753:417953] producer send package 1 ...
2016-12-18 22:04:48.484271 TestRAC4[753:417953] Subscriber 1 get a next value: .Success(package 1)
2016-12-18 22:04:48.484422 TestRAC4[753:417953] producer send package 2 ...
2016-12-18 22:04:48.484665 TestRAC4[753:417953] Subscriber 1 get a next value: .Success(package 2)
2016-12-18 22:04:48.484852 TestRAC4[753:417953] producer send package 3 ...
2016-12-18 22:04:48.485120 TestRAC4[753:417953] Subscriber 1 get a next value: .Success(package 3)
2016-12-18 22:04:49.454274 TestRAC4[753:417953] subscriber 2 get a next value: .Success(package 1)
2016-12-18 22:04:49.454659 TestRAC4[753:417953] subscriber 2 get a next value: .Success(package 2)
2016-12-18 22:04:49.454897 TestRAC4[753:417953] subscriber 2 get a next value: .Success(package 3)

那麼replayLazily(Int.max)這個引數和buffer的相同嗎?我們修改成1或2一看就知道。答案是相同

相關文章