Combine | (III) Combine Operator:時間操作|序列

語言: CN / TW / HK

時間操作操作符(Time Manipulation Operators)

反應式編後的核心思想是隨著時間的推移處理非同步事件流。Combine 提供了一系列允許我們和時間相關的 Operator:隨著時間的推移,序列對值做出處理。Combine 管理序列的時間維度簡單直接,這是 Combine 這的優勢。

本節演示使用的 Playground 基礎程式碼:

```swift import Combine import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true

func example(_ desc: String, _ action:() -> Void) { print("--- (desc) ---") action() }

var subscriptions = Set()

Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .scan(-1, { last, _ in return last + 1 }) .sink { print("($0) second has passed...") } .store(in: &subscriptions) ```

我們使用 needsIndefiniteExecution 使 Playground 無限的執行。example 是已經熟知的幫我們封裝每個 example 的方法。subscriptions 幫我們處理 Subscription。最後的 Timer 幫我們在每 1 秒過去後列印資訊。

Timer 是 Foundation Timer 類的 Combine 擴充套件。 它需要一個 RunLoop 和 RunLoop.Mode,Timer 是可連線的(connectable) Publisher 類的一部分,需要在開始發出值之前被連線。我們使用 autoconnect() 在第一次訂閱時立即連線。我們將在後文了解更多 Timer 的資訊。

時移值

delay(for:tolerance:scheduler:options)

swift func delay<S>( for interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil ) -> Publishers.Delay<Self, S> where S : Scheduler

最基本的時間操作 Operator 是延遲來自 Publisher 的值,使其比實際出現的時間晚。delay(for:tolerance:scheduler:options) Operator 對整個值序列進行時移:每次上游 Publisher 發出一個值時,該 Operator 都會將其保留一段時間,然後在要求的延遲後,在指定的 Scheduler 上發出值。

在 Playground 上新增程式碼:

```swift example("delay") { let valuesPerSecond = 1.0 let delayInSeconds = 1.2

let sourcePublisher = PassthroughSubject<Date, Never>()
let delayedPublisher = sourcePublisher.delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)

let subscription = Timer
    .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
    .autoconnect()
    .subscribe(sourcePublisher)
    .store(in: &subscriptions)

sourcePublisher
    .sink {  print("sourcePublisher: \($0)") }
    .store(in: &subscriptions)

delayedPublisher
    .sink {  print("delayedPublisher: \($0)") }
    .store(in: &subscriptions)

} ```

我們定義了兩個常量,分別表示每秒發出值的數量、延遲發出值的秒。

我們定義了 sourcePublisher,我們將提供 Timer 發出的 Date 型別的值。 值實際的型別其實並不重要,我們只關心 Publisher 在發出值被延遲。

delayPublisher 將延遲來自 sourcePublisher 的值,並將它們傳送到 DispatchQueue.main。我們後續將瞭解所有關於 Scheduler 的更多內容。

建立一個在主執行緒上每秒傳送一個值的 Timer。 使用 autoconnect() 立即啟動它,並通過 sourcePublisher 接收它發出的值。

最後,我們再分別訂閱 sourcePublisherdelayPublisher,新增一些 print 瞭解正在有值被髮出。

我們用彈珠圖描述上述過程,sourcePublisher 發出的值,將被延遲 1.2 秒後,被 delayPublisher 發出:

delay.png

執行 Playground,我們將看到:

--- delay --- 0 second has passed... sourcePublisher: 2022-12-10 07:46:26 +0000 1 second has passed... sourcePublisher: 2022-12-10 07:46:27 +0000 delayedPublisher: 2022-12-10 07:46:26 +0000 2 second has passed... sourcePublisher: 2022-12-10 07:46:28 +0000 delayedPublisher: 2022-12-10 07:46:27 +0000 3 second has passed... sourcePublisher: 2022-12-10 07:46:29 +0000 delayedPublisher: 2022-12-10 07:46:28 +0000 4 second has passed... sourcePublisher: 2022-12-10 07:46:30 +0000 delayedPublisher: 2022-12-10 07:46:29 +0000 ...

收集值

collect(_:options:)

swift func collect<S>( _ strategy: Publishers.TimeGroupingStrategy<S>, options: S.SchedulerOptions? = nil ) -> Publishers.CollectByTime<Self, S> where S : Scheduler

在某些情況下,我們可能需要以指定的時間間隔從 Publisher 那裡收集值,這是一種緩衝形式。 例如,我們想要收集某時間段內的一組值並計算平均值並輸出。

將之前 Playground 的 example 程式碼註釋或刪除,新增以下程式碼:

```swift example("collect") { let valuesPerSecond = 1.0 let collectTimeStride = 3.0

let sourcePublisher = PassthroughSubject<Date, Never>()
let collectedPublisher = sourcePublisher
    .collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))

let subscription = Timer
    .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
    .autoconnect()
    .subscribe(sourcePublisher)
    .store(in: &subscriptions)

sourcePublisher
    .sink {  print("sourcePublisher: \($0)") }
    .store(in: &subscriptions)

collectedPublisher
    .sink {  print("collectedPublisher:\($0)") }
    .store(in: &subscriptions)

} ```

我們定義了兩個常量,分別表示每秒發出值的數量、希望收集一次值的時間間隔。

我們定義了 sourcePublisher,將接收 Timer 發出的 Date 型別的值並再發出。

collectedPublisher 將收集來自 sourcePublisher 的值,並將它們傳送到 DispatchQueue.main

最後,我們再分別訂閱 sourcePublishercollectedPublisher

我們用彈珠圖描述上述過程,sourcePublisher 發出的值,將被 collectedPublisher 收集發出:

collect.png

執行 Playground,我們將看到:

--- collect --- 0 second has passed... sourcePublisher: 2022-12-10 08:16:19 +0000 1 second has passed... sourcePublisher: 2022-12-10 08:16:20 +0000 2 second has passed... sourcePublisher: 2022-12-10 08:16:21 +0000 collectedPublisher:[2022-12-10 08:16:19 +0000, 2022-12-10 08:16:20 +0000, 2022-12-10 08:16:21 +0000] 3 second has passed... sourcePublisher: 2022-12-10 08:16:22 +0000 4 second has passed... sourcePublisher: 2022-12-10 08:16:23 +0000 5 second has passed... sourcePublisher: 2022-12-10 08:16:24 +0000 collectedPublisher:[2022-12-10 08:16:22 +0000, 2022-12-10 08:16:23 +0000, 2022-12-10 08:16:24 +0000] 6 second has passed... sourcePublisher: 2022-12-10 08:16:25 +0000 7 second has passed... sourcePublisher: 2022-12-10 08:16:26 +0000

推遲事件

我們可能希望在使用者搜尋時,請求返回輸入聯想的展示。當然,不能在使用者輸入一個字母時都發送請求,需要有某種機制來控制請求的時機:可以僅在使用者完成一段時間的輸入後。

Combine 提供了兩個可以在這裡為我們提供幫助的 Operator:防抖(Debounce)和節流(Throttle)。

debounce(for:scheduler:options:)

swift func debounce<S>( for dueTime: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil ) -> Publishers.Debounce<Self, S> where S : Scheduler

debounce Operator 經過指定的時間間隔後才會釋出值。用來對從上游 Publisher 傳遞值,和傳遞值之間的時間做控制。此 Operator 可用於處理突發事件流或大量事件流,我們需要將傳遞到下游的值的數量減少到指定的速率。

註釋調之前的程式碼,包括我們記時用的 Timer,新增以下程式碼:

```swift example("debounce") { let subject = PassthroughSubject() let debounced = subject.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)

subject
    .sink {  print("subject: \($0)") }
    .store(in: &subscriptions)

debounced
    .sink {  print("debounced: \($0)") }
    .store(in: &subscriptions)

let bounces:[(Int,TimeInterval)] = [
    (0, 0),
    (1, 0.3),   // 0.3s interval since last index
    (2, 1),     // 0.7s interval since last index
    (3, 1.3),   // 0.3s interval since last index
    (4, 1.5),   // 0.2s interval since last index
    (5, 2.1)    // 0.6s interval since last index
]

for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)
    }
}

} ```

我們讓 subject 每隔 bounces 元組的第二個 TimeInterval 值發出元組的第二個 Int 值。

我們使用 debounced 限定了到達速率為 0.5。即值在 0.5 秒內,沒有下一個值被髮出,則該值可傳遞到下游。

只有值 1、值 4、值 5 後 0.5 秒內,沒有值發出。因此執行 Playground,將輸出:

swift --- debounce --- subject: 0 subject: 1 debounced: 1 subject: 2 subject: 3 subject: 4 debounced: 4 subject: 5 debounced: 5

上述過程,我們使用彈珠圖來描述:

debounced.png

throttle(for:scheduler:latest:)

swift func throttle<S>( for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool ) -> Publishers.Throttle<Self, S> where S : Scheduler

throttle 在指定的時間間隔內,釋出由上游 Publisher 釋出的第一個(latest 為 false)或最後一個(latest 為 true)值。

以上一個例子為基礎,我們在 Playground 中新增程式碼:

```swift example("throttle") { let subject = PassthroughSubject() let throttled = subject.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: false)

subject
    .sink {  print("subject: \($0)") }
    .store(in: &subscriptions)

throttled
    .sink {  print("throttled: \($0)") }
    .store(in: &subscriptions)

let values:[(Int,TimeInterval)] = [
    (0, 0),
    (1, 0.1),
    (2, 0.5),
    (3, 3.5),
    (4, 3.9),
    (5, 4.1),
    (6, 4.4),
]

for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)
    }
}

} ```

由於非同步,時間間隔過短或者卡時間點,都會有不準確的問題。我們放大了示例程式碼中的時間。

我們指定的時間為 1 秒。throttled 直接釋出 0,在 1 秒時,從 (0, 1) 秒區間找到併發布 1。後續由於超過了 1 秒,收到 3 就直接釋出,在 (3.5, 4.5) 秒區間找到併發布 1:

--- throttle --- subject: 0 throttled: 0 subject: 1 subject: 2 throttled: 1 subject: 3 throttled: 3 subject: 4 subject: 5 subject: 6 throttled: 4

上述過程用彈珠圖來描述為:

throttle.png

我們將 latest 改為 true:

swift let throttled = subject.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)

則會輸出每個區間中最新的值:

--- throttle --- subject: 0 throttled: 0 subject: 1 subject: 2 throttled: 2 subject: 3 throttled: 3 subject: 4 subject: 5 subject: 6 throttled: 6

throttle2.png

現在我們有了防抖動和節流的根本區別:

  • 防抖等待它接收到的值的事件暫停,在指定的時間間隔後發出最新的值。
  • 節流等待指定的時間間隔,然後發出它在該時間間隔內收到的第一個或最新的值。

超時

timeout(_:scheduler:options:customError:)

swift func timeout<S>( _ interval: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil, customError: (() -> Self.Failure)? = nil ) -> Publishers.Timeout<Self, S> where S : Scheduler

當超時觸發時, Publisher 要不發出 .finished ,要不發出我們指定的錯誤:

```swift example("timeout") { enum TimeoutError: Error { case timedOut }

let subject = PassthroughSubject<Void, TimeoutError>()
let timedOutSubject1 = subject.timeout(.seconds(3), scheduler: DispatchQueue.main)
let timedOutSubject2 = subject.timeout(.seconds(3), scheduler: DispatchQueue.main, customError: { .timedOut })

timedOutSubject1
    .sink(
        receiveCompletion: { print("timedOutSubject1: \($0)") },
        receiveValue: {  print("timedOutSubject1: \($0)") }
    )
    .store(in: &subscriptions)

timedOutSubject2
    .sink(
        receiveCompletion: { print("timedOutSubject2: \($0)") },
        receiveValue: {  print("timedOutSubject2: \($0)") }
    )
    .store(in: &subscriptions)

} ```

在上述程式碼中,timedOutSubject1 沒有提供 customError 欄位,而 timedOutSubject2 提供了。因此,當 subject超過 3 秒未發出事件,將觸發超時:

--- timeout --- timedOutSubject1: finished timedOutSubject2: failure(Page_Contents.(unknown context at $10f5bfc44).(unknown context at $10f5bfc6c).(unknown context at $10f5bfcac).TimeoutError.timedOut)

timeout.png

時間測量

measureInterval(using:options:)

有時我們需要找出 Publisher 發出的兩個連續值之間經過的時間時,measureInterval Operator 是我們的工具。

新增程式碼:

```swift example("measureInterval") { let subject = PassthroughSubject() let measureSubject = subject.measureInterval(using: DispatchQueue.main)

subject.sink { print("emitted: \($0)") }
.store(in: &subscriptions)

measureSubject.sink { print("Measure emitted: \(Double($0.magnitude) / 1_000_000_000.0)") }
.store(in: &subscriptions)

let bounces:[(Int,TimeInterval)] = [
    (0, 0),
    (1, 0.1),
    (2, 0.5),
    (3, 3.5),
    (4, 3.9),
    (5, 4.1),
    (6, 4.4),
]

for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)
    }
}

} ```

measureSubject 將發出 subject 每次發出的值距離上次發出值的間隔。

由於measureIntervalDispatchQueue 的情況下,TimeInterval 解釋為:使用此型別的值建立的 DispatchTimeInterval,以納秒為單位。因此進行了 Double($0.magnitude) / 1_000_000_000.0 轉換:

--- measureInterval --- Measure emitted: 0.010662459 emitted: 0 Measure emitted: 0.09147425 emitted: 1 Measure emitted: 0.421239375 emitted: 2 Measure emitted: 3.15006375 emitted: 3 Measure emitted: 0.4142835 emitted: 4 Measure emitted: 0.014388208 emitted: 5 Measure emitted: 0.517094042 emitted: 6

如果我們進行以下更改,使用 RunLoop:

swift let measureSubject2 = subject.measureInterval(using: RunLoop.main)

則無需進行上述 Double($0.magnitude) / 1_000_000_000.0 的轉換:

--- measureInterval --- emitted: 0 Measure emitted: Stride(magnitude: 0.008463025093078613) emitted: 1 Measure emitted: Stride(magnitude: 0.0957329273223877) emitted: 2 Measure emitted: Stride(magnitude: 0.4204070568084717) emitted: 3 Measure emitted: Stride(magnitude: 3.1296679973602295) emitted: 4 Measure emitted: Stride(magnitude: 0.4410020112991333) emitted: 5 Measure emitted: Stride(magnitude: 0.2083679437637329) emitted: 6 Measure emitted: Stride(magnitude: 0.108254075050354)

序列操作符(Sequence Operators)

Publisher 本身就是序列。序列 Operator 與 Publisher 的值一起使用,就像 Array 或 Set 一樣——當然,它們是有限序列。考慮到這一點,序列 Operator 主要將 Publisher 作為一個整體來處理,而不是像其他 Operator 那樣處理單個值。此類中的許多 Operator 的名稱和行為與 Swift 標準庫中的對應方法幾乎相同。

尋找值

min() max()

swift func min() -> Publishers.Comparison<Self> func max() -> Publishers.Comparison<Self>

min Operator 幫我們找到 Publisher 發出的最小值。 它是貪婪的,意味著必須等待 Publisher 傳送一個完成事件後, Operator 的下游會發出最小值:

min.png

上述彈珠圖用程式碼表示為:

swift example("min") { let publisher = [1, -5, 10, 0].publisher publisher .print("publisher: ") .min() .sink(receiveCompletion: { print($0) }, receiveValue: { print("Lowest value is \($0)") }) .store(in: &subscriptions) }

執行 Playground,將輸出:

--- min --- publisher: : receive subscription: ([1, -5, 10, 0]) publisher: : request unlimited publisher: : receive value: (1) publisher: : receive value: (-5) publisher: : receive value: (10) publisher: : receive value: (0) publisher: : receive finished Lowest value is -5 finished

將在 publisher 完成後,才會發出最小值再完成。

Combine 知道這些數字中的哪一個是最小值,要歸功於 Int符合 Comparable 協議。我們以在發出 Comparable 型別的 Publisher 上直接使用 min(),無需任何引數。

如果我們的值不符合 Comparable ,我們可以使用 min(by:) Operator 提供自己的比較閉包:

考慮以下示例,你的釋出者發出許多資料,而你希望找到最小的資料。

在以下程式碼示例中,我們比較了 Data 的長度:

swift example("min non-Comparable") { let publisher = ["12345", "ab", "!!@@##$$"] .map { Data($0.utf8) } .publisher .print("publisher") .min(by: { $0.count < $1.count }) .sink(receiveCompletion: { print($0) }, receiveValue: { data in let string = String(data: data, encoding: .utf8)! print("Smallest data is \(string), \(data.count) bytes") }) .store(in: &subscriptions) }

執行 Playground 將輸出:

swift --- min non-Comparable --- publisher: receive subscription: ([5 bytes, 2 bytes, 8 bytes]) publisher: request unlimited publisher: receive value: (5 bytes) publisher: receive value: (2 bytes) publisher: receive value: (8 bytes) publisher: receive finished Smallest data is ab, 2 bytes finished

max Operator 同理,不在進行贅述。

first() last()

swift func first() -> Publishers.First<Self> func last() -> Publishers.Last<Self>

first 讓第一個發出的值通過然後完成。它是 lazy 的,這意味著它不會等待上游釋出者完成,而是會在接收到發出的第一個值時取消訂閱。而 last 是貪婪的,需要在上游發出完成事件後才會完成:

firstAndLast.png

上述彈珠圖用程式碼表示為:

```swift example("first and last") { let publisher = [1, 2, 3, 4].publisher

publisher
    .print("first: ")
    .first()
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print("First value is \($0)") })
    .store(in: &subscriptions)

publisher
    .print("last: ")
    .last()
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print("Last value is \($0)") })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

--- first and last --- first: : receive subscription: ([1, 2, 3, 4]) first: : request unlimited first: : receive value: (1) first: : receive cancel First value is 1 finished last: : receive subscription: ([1, 2, 3, 4]) last: : request unlimited last: : receive value: (1) last: : receive value: (2) last: : receive value: (3) last: : receive value: (4) last: : receive finished Last value is 4 finished

如果我們需要更精細的控制,可以使用 first(where:)last(where:)。 如果有的話,它將發出與提供的條件匹配的第一個、最後一個值:

修改上述程式碼:

```swift example("first and last") { let publisher = [1, 2, 3, 4].publisher

publisher
    .print("first: ")
    .first(where: { $0 % 2 == 0 })
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print("First value is \($0)") })
    .store(in: &subscriptions)

publisher
    .print("last: ")
    .last(where: { $0 % 3 == 0 })
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print("Last value is \($0)") })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

swift --- first and last --- first: : receive subscription: ([1, 2, 3, 4]) first: : request unlimited first: : receive value: (1) first: : receive value: (2) first: : receive cancel First value is 2 finished last: : receive subscription: ([1, 2, 3, 4]) last: : request unlimited last: : receive value: (1) last: : receive value: (2) last: : receive value: (3) last: : receive value: (4) last: : receive finished Last value is 3 finished

如果沒有滿足條件的值,下游也將直接收到完成事件,不會有值發出。

output(at:) output(in:)

output Operator 將查詢上游釋出者在指定索引處發出的值:

output.png

上述彈珠圖用程式碼表示為:

```swift example("output") { let publisher = [0, 1, 2, 3, 4].publisher

publisher
    .print("output at: ")
    .output(at: 1)
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

publisher
    .print("output in: ")
    .output(in: 1...3)
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

--- output --- output at: : receive subscription: ([0, 1, 2, 3, 4]) output at: : request unlimited output at: : receive value: (0) output at: : request max: (1) (synchronous) output at: : receive value: (1) 1 output at: : receive cancel finished output in: : receive subscription: ([0, 1, 2, 3, 4]) output in: : request unlimited output in: : receive value: (0) output in: : request max: (1) (synchronous) output in: : receive value: (1) 1 output in: : receive value: (2) 2 output in: : receive value: (3) 3 output in: : receive cancel finished

該 Operator 會在收到所提供範圍內的所有值後立即取消訂閱。

查詢值

count()

swift func count() -> Publishers.Count<Self>

count Operator 發出單個值, 一旦上游 Publisher 釋出完成事件,Operator 將發出接收到的值的數量:

count.png

上述彈珠圖用程式碼表示為:

```swift example("count") { let publisher = [1, 2, 3, 4].publisher

publisher
    .print("publisher")
    .count()
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

--- count --- publisher: receive subscription: ([1, 2, 3, 4]) publisher: request unlimited publisher: receive value: (1) publisher: receive value: (2) publisher: receive value: (3) publisher: receive value: (4) publisher: receive finished 4 finished

正如預期的那樣,只有在上游 Publisher 傳送完成事件後,才會打印出值 4。

contains(_: contains(where:)

swift func contains(_ output: Self.Output) -> Publishers.Contains<Self> func contains(where predicate: @escaping (Self.Output) -> Bool) -> Publishers.ContainsWhere<Self>

如果上游 Publisher 發出指定的值,則 contains 操作符將發出 true 並立即取消訂閱,如果發出的值都不等於指定的值,則返回 false:

```swift example("contains") { let publisher = [1, 2, 3, 4].publisher

publisher
    .print("publisher")
    .contains(3)
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

publisher
    .print("publisher")
    .contains(where: { $0 % 5 == 0 })
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

--- contains --- publisher: receive subscription: ([1, 2, 3, 4]) publisher: request unlimited publisher: receive value: (1) publisher: receive value: (2) publisher: receive value: (3) publisher: receive cancel true finished publisher: receive subscription: ([1, 2, 3, 4]) publisher: request unlimited publisher: receive value: (1) publisher: receive value: (2) publisher: receive value: (3) publisher: receive value: (4) publisher: receive finished false finished

allSatisfy(_:)

allSatisfy 接受一個閉包,發出一個布林值,指示上游 Publisher 發出的所有值是否與條件匹配。它是貪婪的,若每個值都滿足,會等到上游 Publisher 發出完成完成事件,否則取消訂閱:

```swift example("allSatisfy") { let publisher = stride(from: 0, to: 5, by: 2).publisher publisher .print("publisher") .allSatisfy { $0 % 2 == 0 } .sink(receiveCompletion: { print($0) }, receiveValue: { allEven in print(allEven ? "All numbers are even" : "Something is odd...") }) .store(in: &subscriptions)

publisher
    .print("publisher")
    .allSatisfy { $0 % 3 == 0 }
    .sink(receiveCompletion: { print($0) }, 
        receiveValue: { print($0) })
    .store(in: &subscriptions)

} ```

執行 Playground 將輸出:

--- allSatisfy --- publisher: receive subscription: (Sequence) publisher: request unlimited publisher: receive value: (0) publisher: receive value: (2) publisher: receive value: (4) publisher: receive finished All numbers are even finished publisher: receive subscription: (Sequence) publisher: request unlimited publisher: receive value: (0) publisher: receive value: (2) publisher: receive cancel false finished

reduce(_:_:)

swift func reduce<T>( _ initialResult: T, _ nextPartialResult: @escaping (T, Self.Output) -> T ) -> Publishers.Reduce<Self, T>

reduce Operator 它不查詢特定值或查詢整個 Publisher。 它允許我們根據上游 Publisher 的值,迭代累積一個新值:

reduce.png

我們用程式碼描述上述彈珠圖:

swift example("reduce") { let publisher = ["He", "llo", " ", "Wo", "rld", "!"].publisher publisher .print("publisher") .reduce("") { accumulator, value in accumulator + value } .sink(receiveValue: { print("Reduced into: \($0)") }) .store(in: &subscriptions) }

在此程式碼中,我們建立一個發出六個字串的 publisher。將 reduce 與空字串一起使用,將發出的值附加到它上面,建立最終的字串結果:

--- reduce --- publisher: receive subscription: (["He", "llo", " ", "Wo", "rld", "!"]) publisher: request unlimited publisher: receive value: (He) publisher: receive value: (llo) publisher: receive value: ( ) publisher: receive value: (Wo) publisher: receive value: (rld) publisher: receive value: (!) publisher: receive finished Reduced into: Hello World!

reduce 的第二個引數是一個閉包,它接受兩個某種型別的值並返回一個相同型別的值。在 Swift 中,+ 也是一個匹配該簽名的函式,我們完全可以改寫程式碼:

swift example("reduce") { let publisher = ["He", "llo", " ", "Wo", "rld", "!"].publisher publisher .print("publisher") .reduce("", +) .sink(receiveValue: { print("Reduced into: \($0)") }) .store(in: &subscriptions) }

內容參考