第八章 Combine的序列操作符(min、max、first、last、output)

1,415 阅读3分钟

当您意识到发布者只是序列本身时,序列操作符最容易理解。序列操作符处理发布者的值,很像数组或集合——当然,它们只是有限序列! 考虑到这一点,序列运算符主要将发布者作为一个整体处理,而不是像其他操作符那样处理单个值。 该类别中的许多运算符与 Swift 标准库中的对应运算符具有几乎相同的名称和行为。

Finding values

根据不同的标准定位发布者发出的特定值。

min

min 操作符可让您找到发布者发出的最小值。它是贪婪的,这意味着它必须等待发布者发送一个 .finished 完成事件。一旦发布者完成,这个操作符只会发布最小值到下游。

img1490.jpg

在playground中加入如下代码:

  // 1
  let publisher = [1, -50, 246, 0].publisher

  // 2
  publisher
    .print("publisher")
    .min()
    .sink(receiveValue: { print("Lowest value is \($0)") })
    .store(in: &subscriptions)

运行playground,得到结果:

publisher: receive subscription: ([1, -50, 246, 0])
publisher: request unlimited
publisher: receive value: (1)
publisher: receive value: (-50)
publisher: receive value: (246)
publisher: receive value: (0)
publisher: receive finished
Lowest value is -50

如您所见,发布者发出其所有值并完成,然后 min 找到最小值并将其发送到下游以将其打印出来。 但是等等,Combine 如何知道这些数字中的哪一个是最小值?嗯,这要归功于数值符合 Comparable 协议这一事实。您可以在发出 Comparable-conformed 类型的发布者上直接使用 min() ,无需任何参数。 但是如果您的值不符合 Comparable 会发生什么?幸运的是,您可以使用 min(by:) 运算符提供自己的比较器闭包。 考虑以下示例,其中您的发布者发出许多数据,而您想找到最小的数据

  // 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)

例子中publisher发布的数据不符合Comparable协议,所以用min(by:)来查找其中的最小值。

运行playground,得到结果:

publisher: receive subscription: ([5 bytes, 2 bytes, 11 bytes])
publisher: request unlimited
publisher: receive value: (5 bytes)
publisher: receive value: (2 bytes)
publisher: receive value: (11 bytes)
publisher: receive finished
Smallest data is ab, 2 bytes

max

和min的原理一致,但功能相反。max找到发布者发布的最大值。

img1500.jpg 在playground中加入如下代码:

  // 1
  let publisher = ["A", "F", "Z", "E"].publisher

  // 2
  publisher
    .print("publisher")
    .max()
    .sink(receiveValue: { print("Highest value is \($0)") })
    .store(in: &subscriptions)

运行playground,得到结果:

publisher: receive subscription: (["A", "F", "Z", "E"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (F)
publisher: receive value: (Z)
publisher: receive value: (E)
publisher: receive finished
Highest value is Z

就像 min 一样,max 是贪婪的,必须等待上游发布者完成其值的发布,然后才能确定最大值。

let publisher = PassthroughSubject<Int, Never>()

publisher
    .print("publisher")
    .max()
    .sink(receiveValue: { print("Max value is \($0)") })
    .store(in: &subscriptions)

publisher4.send(1)
publisher4.send(2)
publisher4.send(completion: .finished)

运行playground,得到结果:

publisher4: receive subscription: (PassthroughSubject)
publisher4: request unlimited
publisher4: receive value: (1)
publisher4: receive value: (2)
publisher4: receive finished
Max value is 2

像上面这个例子,如果我们注释掉

publisher4.send(completion: .finished)

则无法在控制台看到"Max value is ..."的打印,因为publisher没有发布完成,所以max(同理min也是一样)无法工作。

first

first让Publisher第一个发出的值通过然后完成。它是惰性的,这意味着它不会等待上游发布者完成,而是在收到第一个发出的值时取消订阅。

img1510.jpg

在playground中加入如下代码:

  // 1
  let publisher = ["A", "B", "C"].publisher

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

运行playground,得到结果:

publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive cancel
First value is A

可以看出,一旦 first() 获得发布者的第一个值,它就会取消对上游发布者的订阅。 如果您正在寻找更精细的控制,您也可以使用 first(where:)。就像它在 Swift 标准库中的对应函数一样,它将发出与提供的闭包匹配的第一个值。

参考如下代码:

  // 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)

运行playground,得到结果:

publisher: receive subscription: (["J", "O", "H", "N"])
publisher: request unlimited
publisher: receive value: (J)
publisher: receive value: (O)
publisher: receive value: (H)
publisher: receive cancel
First match is H

这个demo中我们用first(where:)查找“Hello World”字符串中包含的第一个字符,最终得到结果"H",然后这个订阅就被取消了。

last

last 与 first 完全一样,除了它发出发布者发出的最后一个值。这意味着它是贪婪的,必须等待上游发布者完成。

img1520.jpg

在playground中加入如下代码:

  // 1
  let publisher = ["A", "B", "C"].publisher

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

运行playground,得到结果:

publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive finished
Last value is C

类似max和min这些贪婪操作符,last需要发布者完成发布才能让其执行,所以您可以尝试用PassthroughSubject发布数据,然后不发布completion: .finished状态,观察其结果。这里就不再举例了。

output(at:)

output操作符在指定索引处查找上游发布者发出的值,成功后会取消订阅。 ![[img1526.jpg]]

在playground加入如下代码:

  // 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)

运行playground,得到结果:

publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: request max: (1) (synchronous)
publisher: receive value: (B)
Value at index 1 is B
`publisher: receive cancel`

output(in:)

output(at:) 发出在指定索引处发出的单个值,而 output(in:) 发出索引在提供范围内的所有值,随后取消订阅。

img1537.jpg

在playground中加入如下代码:

  // 1
  let publisher = ["A", "B", "C", "D", "E"].publisher

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

运行playground,得到结果:

Value in range: B
Value in range: C
Value in range: D
`finished`