第八章 Combine的序列操作符(count、contains、allSatisfy、reduce)

415 阅读2分钟

本节的操作符处理发布者发出的整个值域,但不会发布他们原有的任何值。相反,这些运算符会发出一个不同的值,代表对发布者的一些查询处理。一个很好的例子是计数操作符count

count

计数操作符将发出一个单一的值 - 一旦发布者发送一个 .finished 完成事件,上游发布者发出的值的数量

img1543.jpg

在playground中加入如下代码:

  // 1
  let publisher = ["A", "B", "C"].publisher
    
  // 2
  publisher
    .print("publisher")
    .count()
    .sink(receiveValue: { print("I have \($0) items") })
    .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
I have 3 items

Demo中使用 count() 发出一个值,指示上游发布者发出的值的数量

contains

如果上游发布者发出了指定的值,则 contains 操作符将发出 true 并取消订阅,如果没有发出的值等于指定的值,则操作符发出false。

img1553.jpg

在playground加入如下代码:

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

  // 2
  publisher
    .print("publisher")
    .contains(letter)
    .sink(receiveValue: { contains in
      // 3
      print(contains ? "Publisher emitted \(letter)!"
                     : "Publisher never emitted \(letter)!")
    })
    .store(in: &subscriptions)

运行playground,得到结果:

publisher: receive subscription: (["A", "B", "C", "D", "E"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive cancel
Publisher emitted C!

contains(where:)

当希望为contains增加查找条件时,或检查是否存在不符合 Comparable 的发出值时。可以使用 contains(where:)

在playground中加入如下代码:

  // 1
  struct Person {
    let id: Int
    let name: String
  }

  // 2
  let people = [
    (123, "Shai Mishali"),
    (777, "Marin Todorov"),
    (214, "Florent Pillet")
  ]
  .map(Person.init)
  .publisher

  // 3
  people
    .contains(where: { $0.id == 800 })
    .sink(receiveValue: { contains in
      // 4
      print(contains ? "Criteria matches!"
                     : "Couldn't find a match for the criteria")
    }) 
  1. 定义一个带有 id 和名称的 Person 结构。
  2. 创建一个发布三个不同的 People 实例的发布者。
  3. 使用 contains 查看其中任何一个的 id 是否为 800。
  4. 根据发出的结果打印适当的消息。

运行playground,得到结果:

Couldn't find a match for the criteria

将原来的

.contains(where: { $0.id == 800 })

改为

.contains(where: { $0.id == 800 || $0.name == "Marin Todorov" })

再次运行playground,得到结果:

Criteria matches!

allSatisfy

发布一个布尔值,指示是否所有接收到的元素都通过给定的闭包中的条件。

它是贪婪的,因此会等到上游发布者发出 .finished 完成事件。

img1567.jpg

在playground中加入如下代码:

let targetRange = (-1...100)
let numbers = [-1, 0, 10, 5]
numbers.publisher
    .allSatisfy { targetRange.contains($0) }
    .sink { print("\($0)") }

// Prints: "true"

使用 allSatisfy(_:) 运算符来确定流中的所有元素是否满足您提供的条件。当此发布者收到一个元素时,它会针对该元素在闭包中判断。如果返回 false,则发布者生成 false 并结束。如果上游发布者正常完成,则此发布者产生一个true并完成。

reduce

提供一个闭包,用于收集流的每个元素并在上游publisher发布.finished后得出最终结果。

img1578.jpg 闭包接收所有元素,并在上游发布者完成时发布累计处理的值(如累加)。如果reduce(::) 收到上游发布者的错误,reduce将其传递给下游订阅者,发布者将终止并且不发布任何值。

在playground中加入如下代码:

let numbers = (0...10)
cancellable = numbers.publisher
    .reduce(0, { accum, next in accum + next })
    .sink { print("\($0)") }

运行playground,得到结果:

55

在这个示例中,reduce(::) 运算符收集从其上游发布者接收的所有整数值并计算累加结果。

注意,这里可以简化代码写法,将reduce()行改为

.reduce("", +)

第三章中的scan和reduce很相似,主要区别在于 scan 为每个发出的值发出累积值,而一旦上游发布者发送 .finished 完成事件,reduce 就会发出单个累积值。随意将上面的例子中的reduce更改为scan,然后自己尝试一下。

本章key points

  • 您可以使用 min 和 max 分别发出发布者发出的最小值或最大值。
  • first、last 和 output(at:) 在您想查找在特定索引处发出的值时很有用。使用 output(in:) 查找在索引范围内发出的值。
  • first(where:) 和 last(where:) 每个都使用一个谓词来确定它应该让哪些值通过。
  • count、contains 和 allSatisfy 等运算符不会发出发布者发出的值。相反,它们会根据发出的值发出不同的值。
  • contains(where:) 使用谓词来确定发布者是否包含给定的值。
  • 使用 reduce 将发出的所有值累积为单个值