Sequence operators are easiest to understand when you realize that the publisher is just the sequence itself. Sequence operators handle publisher values, much like arrays or collections — of course, they’re just finite sequences! With this in mind, sequence operators deal primarily with the publisher as a whole, rather than with individual values as other operators do. Many of the operators in this category have nearly identical names and behaviors to their counterparts in the Swift standard library.
Finding values
Locate specific values issued by publishers based on different criteria.
min
The min operator lets you find the minimum value issued by the publisher. It is greedy, which means it must wait for the publisher to send a.finished event. Once the publisher is done, this operator publishes only the minimum value downstream.
Add the following code to our playground:
/ / 1
let publisher = [1.-50.246.0].publisher
/ / 2
publisher
.print("publisher")
.min()
.sink(receiveValue: { print("Lowest value is \ [$0)") })
.store(in: &subscriptions)
Copy the code
Run playground to get the result:
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
Copy the code
As you can see, the publisher issues all its values and is done, then Min finds the minimum value and sends it downstream to print it out. But wait, how does Combine know which of these numbers is the minimum? Well, this is thanks to the fact that values are subject to the Comparable agreement. You can use min() directly on publishers that issue the Comparable-conformed type without any arguments. But what happens if your value is not Comparable? Fortunately, you can provide your own comparator closure using the min(by:) operator. Consider the following example, where your publisher emits a lot of data and you want to find the smallest
/ / 1
let publisher = ["12345"."ab"."hello world"]
.map { Data($0.utf8) } // [Data]
.publisher // Publisher<Data, Never>
/ / 2
publisher
.print("publisher")
.min(by: { $0.count < The $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)
Copy the code
The publisher data in the example does not comply with the Comparable protocol, so min(by:) is used to find the minimum value.
Run playground to get the result:
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
Copy the code
max
The principle is the same as min, but the function is opposite. Max finds the maximum value published by the publisher.
Add the following code to our playground:
/ / 1
let publisher = ["A"."F"."Z"."E"].publisher
/ / 2
publisher
.print("publisher")
.max()
.sink(receiveValue: { print("Highest value is \ [$0)") })
.store(in: &subscriptions)
Copy the code
Run playground to get the result:
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
Copy the code
Like MIN, Max is greedy and must wait for upstream publishers to publish its value before the maximum can be determined.
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)
Copy the code
Run playground to get the result:
publisher4: receive subscription: (PassthroughSubject)
publisher4: request unlimited
publisher4: receive value: (1)
publisher4: receive value: (2)
publisher4: receive finished
Max value is 2
Copy the code
Like in the example above, if we comment out
publisher4.send(completion: .finished)
Copy the code
You cannot see “Max value is…” on the console. Because Publisher has not been published, Max (same with Min) cannot work.
first
First lets Publisher pass the first value sent out and completes. It is lazy, meaning that it does not wait for upstream publishers to complete, but rather unsubscribe when it receives the first emitted value.
Add the following code to our playground:
/ / 1
let publisher = ["A"."B"."C"].publisher
/ / 2
publisher
.print("publisher")
.first()
.sink(receiveValue: { print("First value is \ [$0)") })
.store(in: &subscriptions)
Copy the code
Run playground to get the result:
publisher: receive subscription: (["A"."B"."C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive cancel
First value is A
Copy the code
As you can see, once first() gets the first value from the publisher, it unsubscribs to the upstream publisher. If you are looking for finer control, you can also use first(where:). Just like its counterpart in the Swift standard library, it emits the first value that matches the supplied closure.
Refer to the following code:
/ / 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)
Copy the code
Run playground to get the result:
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
Copy the code
In this demo, we use first(where:) to find the first character contained in the “Hello World” string, resulting in “H”, and the subscription is cancelled.
last
Last is exactly the same as first, except that it emits the last value emitted by the publisher. This means that it is greedy and must wait for upstream publishers to complete.
Add the following code to our playground:
/ / 1
let publisher = ["A"."B"."C"].publisher
/ / 2
publisher
.print("publisher")
.last()
.sink(receiveValue: { print("Last value is \ [$0)") })
.store(in: &subscriptions)
Copy the code
Run playground to get the result:
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
Copy the code
Greedy operators like Max and min require the publisher to publish last in order for it to execute, so you can try publishing data with PassthroughSubject and then not publish completion:.finished and observe the results. I won’t give you any examples here.
output(at:)
The output operator looks for the value issued by the upstream publisher at the specified index and unsubscribes on success. ! [[img1526.jpg]]
Add the following code to 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)
Copy the code
Run playground to get the result:
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`
Copy the code
output(in:)
Output (at:) emits a single value emitted at the specified index, while output(in:) emits all values within the supplied range of the index and then unsubscribes.
Add the following code to our 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)
Copy the code
Run playground to get the result:
Value in range: B
Value in range: C
Value in range: D
`finished`
Copy the code