Leave a rating/review
On their surface, Publishers are really just sequences, and set of sequence operators in Combine let you work with the sequence as a whole instead of individual values. As we go through these operators, you’ll notice that many have the same names as functions from the Swift Standard Library - but be sure to note any differences between those functions and the operators in Combine when you use them in your code.
The min operator, which is greedy, lets you find the minimum value emitted by a publisher. It has to wait for the publisher to send a .finished completion event so it knows there are no other values that could possibly change what it considers to be the minimum value. You can see this in the marble diagram. Nothing gets emitted to the downstream consumer until the completion event - represented here by the vertical line - gets sent. In this case, -50, the minimum value from the publisher, gets sent. Let’s take a look at this in a playground.
Setup an array with the values 1, -50, 246 and 0, and use the publisher method on the array to get a publisher we can use in the example.
example(of: "min") {
// 1
let publisher = [1, -50, 246, 0].publisher
Then, build the publisher-operator-sink pipeline, adding in a print operator to help see what’s happening during the execution of the pipeline.
// 2
publisher
.print("publisher")
.min()
.sink(receiveValue: { print("Lowest value is \($0)") })
.store(in: &subscriptions)
}
Run the playground. In the console, you’ll see that the operator takes in all of the values from the publisher before determining the minimum value - reflecting the operator’s greediness.
As you might imagine, if Combine has a min operator, it has to have a max operator. You would also be right if you guessed that instead of returning the minimum value from the publisher, it returns the maximum value. Max is also a greedy operator, requiring all of the values from the publisher before it can determine the max value. Let’s look at that in a demo.
Make an array with strings “A”, “F”, “Z”, and “E”, and use the publisher method on the array to get a publisher we can use in the example.
example(of: "max") {
// 1
let publisher = ["A", "F", "Z", "E"].publisher
Then, build the publisher-operator-sink pipeline, adding in a print operator to help see what’s happening during the execution of the pipeline.
// 2
publisher
.print("publisher")
.max()
.sink(receiveValue: { print("Highest value is \($0)") })
.store(in: &subscriptions)
}
Run the playground.
In the console, you’ll see that max, like min, waits for all of the values to be emitted by the publishers before determining the max value. Something to take note of here is that strings and integers are Comparable in Swift, so there was no need to specify how to determine if a value was smaller or larger than another value. If you have your own value that doesn’t adopt Comparable, min(by:) and max(by:) can be used to pass in a closure to help the operator determine which value to return.
The count operator does exactly that - it counts the number of values emitted by the publisher. Again, this operator is greedy, since it has to get all of the values before it can determine how many values were emitted by the publisher. Let’s take a look.
Make an array with the strings “A”, “B” and “C”, and use the publisher method on the array to get a publisher we can use in the example.
example(of: "count") {
let publisher = ["A", "B", "C"].publisher
Then, build the publisher-operator-sink pipeline, adding in a print operator to help see what’s happening during the execution of the pipeline.
publisher
.print("publisher")
.count()
.sink(receiveValue: { print("I have \($0) items") })
.store(in: &subscriptions)
}
Run the playground. In the console, you’ll see that “I have 3 items” is printed once all of the values have been emitted by the publisher.
Finally, let’s look at the output operator. This operator will only return values that were emitted at specific indices in the upstream publisher. There are 2 varieties. output(at:) returns the value at the specified index, as you can see in the marble diagram.
output(in:) returns values in a specific range of indices. In the marble diagram, the requested indices are 1 through 3 inclusive, so the values “B”, “C”, and “D” are passed to the downstream consumer. Let’s look at both of these in a demo.
Make an array with the strings “A”, “B” and “C”, and use the publisher method on the array to get a publisher we can use in the example.
example(of: "output(at:)") {
let publisher = ["A", "B", "C"].publisher
Then, build the publisher-operator-sink pipeline, adding in a print operator to help see what’s happening during the execution of the pipeline.
publisher
.print("publisher")
.output(at: 1)
.sink(receiveValue: { print("Value at index 1 is \($0)") })
.store(in: &subscriptions)
}
Run the playground. In the console, the print statement appears when index 1 is reached, stating that “Value at index 1 is B”, which is correct.
To examine the behavior of output(in:), make an array with the strings “A”, “B”, “C”, “D”, and “E”, and use the publisher method on the array to get a publisher we can use in the example.
example(of: "output(in:)") {
let publisher = ["A", "B", "C", "D", "E"].publisher
Then, build the publisher-operator-sink pipeline, adding in a print operator to help see what’s happening during the execution of the pipeline.
publisher
.output(in: 1...3)
.sink(receiveCompletion: { print($0) },
receiveValue: { print("Value in range: \($0)") })
.store(in: &subscriptions)
}
Run the playground. In the console, you’ll see statements for the values “B”, “C” and “D”, corresponding to indices 1, 2, and 3 from the original publisher.
In this episode we looked at a series of sequencing operators in Combine.
The min operator returns the minimum value that the publisher produces. The max operator returns the maximum value that the publisher produces.
The count operator returns the number of emitted values of the publisher.
The output operator only returns values at a specific index (or range of indicies) of the publisher.
If I look at what’s next in our sequence of topics…. it’s a challenge! When you’re ready to test your knowledge about sequencing operators, hop over to the next episode.