example(of: "min non-Comparable") { // 1 let publisher = ["12345", "ab", "hello world"] .map { Data($0.utf8) } // [Data] .publisher // Publisher<Data, Never> // 2 publisher .print("publisher") .min(by: { $0.count < $1.count }) .sink(receiveValue: { data in // 3 let string = String(data: data, encoding: .utf8)! print("Smallest data is \(string), \(data.count) bytes") }) .store(in: &subscriptions)}
在上面的代码中:
你创建一个发布者,该发布者发出三个从各种字符串创建的 Data 对象。
由于 Data 不符合 Comparable,因此使用 min(by:) 操作符查找字节数最少的 Data 对象。
将最小的 Data 对象转换回字符串并打印出来。
运行你的 Playground,你将在控制台中看到以下内容:
——— Example of: min non-Comparable ———publisher: receive subscription: ([5 bytes, 2 bytes, 11 bytes])publisher: request unlimitedpublisher: receive value: (5 bytes)publisher: receive value: (2 bytes)publisher: receive value: (11 bytes)publisher: receive finishedSmallest data is ab, 2 bytes
与前面的示例一样,发布者发出其所有 Data 对象并完成,然后 min(by:) 找到并发出具有最小字节大小的数据,并且接收器将其打印出来。
max
正如你所猜测的,max 的工作方式与 min 完全相同,只是它找到了发布者发出的最大值:
将以下代码添加到你的 Playground 以尝试此示例:
example(of: "max") { // 1 let publisher = ["A", "F", "Z", "E"].publisher // 2 publisher .print("publisher") .max() .sink(receiveValue: { print("Highest value is \($0)") }) .store(in: &subscriptions)}
在以下代码中,你:
创建一个发出四个不同字母的发布者。
使用 max 操作符找出数值最高的字母并打印出来。
运行 Playground。你将在 Playground 中看到以下输出:
——— Example of: max ———publisher: receive subscription: (["A", "F", "Z", "E"])publisher: request unlimitedpublisher: receive value: (A)publisher: receive value: (F)publisher: receive value: (Z)publisher: receive value: (E)publisher: receive finishedHighest value is Z
与 min 完全一样,max 是贪婪的,必须等待上游发布者完成其值的发送,然后才能确定最大值。 在这种情况下,该值为 Z。
注意:与 min 完全一样,max 也有一个伴随的 max(by:) 操作符,它接受一个谓词来确定在 non-Comparable 值中发出的最大值。
first
虽然 min 和 max 操作符处理在某个未知索引处查找发布的值,但本节中的其余操作符处理在特定位置查找发出的值,从 first 操作符符开始。
第一个操作符类似于 Swift 的 collection 的 first 属性,它让第一个发出的值通过然后完成。它是惰性的,这意味着它不会等待上游发布者完成,而是会在接收到发出的第一个值时取消订阅。
将上面的示例添加到你的 Playground:
example(of: "first") { // 1 let publisher = ["A", "B", "C"].publisher // 2 publisher .print("publisher") .first() .sink(receiveValue: { print("First value is \($0)") }) .store(in: &subscriptions)}
在上面的代码中,你:
创建一个发出三个字母的发布者。
使用 first() 只让第一个发出的值通过并打印出来。
运行你的 Playground 并查看控制台:
——— Example of: first ———publisher: receive subscription: (["A", "B", "C"])publisher: request unlimitedpublisher: receive value: (A)publisher: receive cancelFirst value is A
一旦 first() 获得发布者的第一个值,它就会取消对上游发布者的订阅。
如果你正在寻找更精细的控制,你也可以使用 first(where:)。 就像它在 Swift 标准库中的对应物一样,它将发出与提供的条件匹配的第一个值——如果有的话。
将以下示例添加到你的 Playground:
example(of: "first(where:)") { // 1 let publisher = ["J", "O", "H", "N"].publisher // 2 publisher .print("publisher") .first(where: { "Hello World".contains($0) }) .sink(receiveValue: { print("First match is \($0)") }) .store(in: &subscriptions)}
在此代码中:
创建一个发出四个字母的发布者。
使用 first(where:) 操作符查找 Hello World 中包含的第一个字母,然后将其打印出来。
运行 Playground,你将看到以下输出:
——— Example of: first(where:) ———publisher: receive subscription: (["J", "O", "H", "N"])publisher: request unlimitedpublisher: receive value: (J)publisher: receive value: (O)publisher: receive value: (H)publisher: receive cancelFirst match is H
在上面的示例中,操作符检查 Hello World 是否包含发出的字母,直到找到第一个匹配项:H。找到那么多后,它会取消订阅并发出字母让 sink 打印出来。
last
正如 min 有一个对立面,max,first 也有一个对应的操作符:last!
last 的工作方式与 first 完全相同,只是它发出发布者发出的最后一个值。 这意味着它也是贪婪的,必须等待上游发布者完成:
将此示例添加到你的 Playground:
example(of: "last") { // 1 let publisher = ["A", "B", "C"].publisher // 2 publisher .print("publisher") .last() .sink(receiveValue: { print("Last value is \($0)") }) .store(in: &subscriptions)}
在此代码中:
创建一个将发出三个字母并完成的发布者。
使用 last 操作符只发出最后发布的值并打印出来。
运行 Playground,你将看到以下输出:
——— Example of: last ———publisher: receive subscription: (["A", "B", "C"])publisher: request unlimitedpublisher: receive value: (A)publisher: receive value: (B)publisher: receive value: (C)publisher: receive finishedLast value is C
last 等待上游发布者发送一个 .finished 完成事件,此时它向下游发送最后一个发出的值,以便在接收器中打印出来。
注意:与 first 完全一样,last 也有一个 last(where:) 重载,它发出匹配指定条件的发布者发出的最后一个值。
output(at:)
本节中的最后两个操作符在 Swift 标准库中没有对应的操作符。 output 操作符将查找上游发布者在指定索引处发出的值。
你将从 output(at:) 开始,它只发出在指定索引处发出的值:
将以下代码添加到你的 Playground 以尝试此示例:
example(of: "output(at:)") { // 1 let publisher = ["A", "B", "C"].publisher // 2 publisher .print("publisher") .output(at: 1) .sink(receiveValue: { print("Value at index 1 is \($0)") }) .store(in: &subscriptions)}
在上面的代码中:
创建一个发出三个字母的发布者。
使用 output(at:) 只允许在索引 1 处发出的值——即第二个值。
在你的 Playground 中运行该示例并查看你的控制台:
——— Example of: output(at:) ———publisher: receive subscription: (["A", "B", "C"])publisher: request unlimitedpublisher: receive value: (A)publisher: request max: (1) (synchronous)publisher: receive value: (B)Value at index 1 is Bpublisher: receive cancel
在这里,输出表明索引 1 处的值是 B。但是,你可能已经注意到另一个有趣的事实:操作符在每个发出的值之后都需要一个值,因为它知道它只是在寻找单个项目。虽然这是特定操作符的实现细节,但它提供了有关 Apple 如何设计一些自己的内置 Combine 操作符以利用背压的有趣见解。