时间操作操作符(Time Manipulation Operators)
反应式编后的核心思想是随着时间的推移处理异步事件流。Combine 提供了一系列允许我们和时间相关的 Operator:随着时间的推移,序列对值做出处理。Combine 管理序列的时间维度简单直接,这是 Combine 这的优势。
本节演示使用的 Playground 基础代码:
import Combine
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
func example(_ desc: String, _ action:() -> Void) {
print("--- \(desc) ---")
action()
}
var subscriptions = Set<AnyCancellable>()
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)
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 上添加代码:
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
接收它发出的值。
最后,我们再分别订阅 sourcePublisher
和 delayPublisher
,添加一些 print
了解正在有值被发出。
我们用弹珠图描述上述过程,sourcePublisher
发出的值,将被延迟 1.2 秒后,被 delayPublisher
发出:
运行 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:)
func collect<S>(
_ strategy: Publishers.TimeGroupingStrategy<S>,
options: S.SchedulerOptions? = nil
) -> Publishers.CollectByTime<Self, S> where S : Scheduler
在某些情况下,我们可能需要以指定的时间间隔从 Publisher 那里收集值,这是一种缓冲形式。 例如,我们想要收集某时间段内的一组值并计算平均值并输出。
将之前 Playground 的 example
代码注释或删除,添加以下代码:
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
。
最后,我们再分别订阅 sourcePublisher
和 collectedPublisher
。
我们用弹珠图描述上述过程,sourcePublisher
发出的值,将被 collectedPublisher
收集发出:
运行 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:)
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,添加以下代码:
example("debounce") {
let subject = PassthroughSubject<Int, Never>()
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,将输出:
--- debounce ---
subject: 0
subject: 1
debounced: 1
subject: 2
subject: 3
subject: 4
debounced: 4
subject: 5
debounced: 5
上述过程,我们使用弹珠图来描述:
throttle(for:scheduler:latest:)
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 中添加代码:
example("throttle") {
let subject = PassthroughSubject<Int, Never>()
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
上述过程用弹珠图来描述为:
我们将 latest
改为 true:
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
现在我们有了防抖动和节流的根本区别:
- 防抖等待它接收到的值的事件暂停,在指定的时间间隔后发出最新的值。
- 节流等待指定的时间间隔,然后发出它在该时间间隔内收到的第一个或最新的值。
超时
timeout(_:scheduler:options:customError:)
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
,要不发出我们指定的错误:
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)
时间测量
measureInterval(using:options:)
有时我们需要找出 Publisher 发出的两个连续值之间经过的时间时,measureInterval
Operator 是我们的工具。
添加代码:
example("measureInterval") {
let subject = PassthroughSubject<Int, Never>()
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
每次发出的值距离上次发出值的间隔。
由于measureInterval
在 DispatchQueue
的情况下,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
:
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()
func min() -> Publishers.Comparison<Self>
func max() -> Publishers.Comparison<Self>
min
Operator 帮我们找到 Publisher 发出的最小值。 它是贪婪的,意味着必须等待 Publisher 发送一个完成事件后, Operator 的下游会发出最小值:
上述弹珠图用代码表示为:
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
的长度:
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 将输出:
--- 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()
func first() -> Publishers.First<Self>
func last() -> Publishers.Last<Self>
first
让第一个发出的值通过然后完成。它是 lazy 的,这意味着它不会等待上游发布者完成,而是会在接收到发出的第一个值时取消订阅。而 last
是贪婪的,需要在上游发出完成事件后才会完成:
上述弹珠图用代码表示为:
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:)
。 如果有的话,它将发出与提供的条件匹配的第一个、最后一个值:
修改上述代码:
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 将输出:
--- 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 将查找上游发布者在指定索引处发出的值:
上述弹珠图用代码表示为:
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()
func count() -> Publishers.Count<Self>
count
Operator 发出单个值, 一旦上游 Publisher 发布完成事件,Operator 将发出接收到的值的数量:
上述弹珠图用代码表示为:
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:)
func contains(_ output: Self.Output) -> Publishers.Contains<Self>
func contains(where predicate: @escaping (Self.Output) -> Bool) -> Publishers.ContainsWhere<Self>
如果上游 Publisher 发出指定的值,则 contains 操作符将发出 true 并立即取消订阅,如果发出的值都不等于指定的值,则返回 false:
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 发出完成完成事件,否则取消订阅:
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(_:_:)
func reduce<T>(
_ initialResult: T,
_ nextPartialResult: @escaping (T, Self.Output) -> T
) -> Publishers.Reduce<Self, T>
reduce
Operator 它不查找特定值或查询整个 Publisher。 它允许我们根据上游 Publisher 的值,迭代累积一个新值:
我们用代码描述上述弹珠图:
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 中,+
也是一个匹配该签名的函数,我们完全可以改写代码:
example("reduce") {
let publisher = ["He", "llo", " ", "Wo", "rld", "!"].publisher
publisher
.print("publisher")
.reduce("", +)
.sink(receiveValue: { print("Reduced into: \($0)") })
.store(in: &subscriptions)
}
内容参考
- Combine | Apple Developer Documentation;
- 来自 Kodeco 的书籍《Combine: Asynchronous Programming with Swift》;
- 对上述 Kodeco 书籍的汉语自译版 《Combine: Asynchronous Programming with Swift》 的整理、修改、补充更新。