In this chapter, you’ll learn about a more complex but useful class of operators: the combinatorial operators. This set of operators allows you to combine events emitted by different publishers and create meaningful data combinations in your composite code. Why are combinations useful? Consider a form that contains multiple user inputs, such as a user name, a password, and a check box. You need to combine these different pieces of data to form a publisher that contains all the information you need.
Here you will slowly start using a set of operators that are all about adding values at the beginning of your publisher. In other words, you will use them to add values emitted before any from the original publisher. In this section, you’ll learn about prepend(Output…) , Prepend (Sequence) and prepend(Publisher).
Prepend series of operators
prepend(Output…)
This prepend uses… The syntax uses variable argument lists. This means that it can take any number of values, as long as they are of the same type as the original publisher’s output.
Add the following code to our playground:
/ / 1
let publisher = [3.4].publisher
/ / 2
publisher
.prepend(1.2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create a release number
3, 4
Publisher of. - Use prepend to add the numbers 1 and 2 before the publisher’s own value.
Run playground to get the result:
1
2
3
4
Copy the code
Of course, we can continue to add values in front of the playground, such as adding a line after the original prepend line. The modified source code reads:
/ / 1
let publisher = [3.4].publisher
/ / 2
publisher
.prepend(1.2)
.prepend(-1.0)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
Run playground to get the result:
-1
0
1
2
3
4
Copy the code
Note that the order of operations is crucial here. The last prefix affects upstream first, which means -1 and 0 come first, then 1 and 2, and finally the value of the original publisher
prepend(Sequence)
This prepend is similar to the previous one, except that it takes as input any object that conforms to the Sequence. For example, it might require an array or a collection.
Add the following code to our playground:
/ / 1
let publisher = [5.6.7].publisher
/ / 2
publisher
.prepend([3.4])
.prepend(Set(1.2))
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create a publisher that publishes the numbers 5, 6, and 7.
- The Combine process prepends (Sequence) twice to the original publisher. Add values from the array once and from the collection a second time
Run playground to get the result:
1
2
3
4
5
6
7
Copy the code
An important fact to remember about collections, as opposed to arrays, is that they are unordered, so there is no guarantee of the order in which items are emitted. This means that the first two values in the above example could be 1 and 2, or 2 and 1.
prepend(Publisher)
The first two operators preappend values to existing publishers. But what if you have two different publishers and you want to combine their values? You can use prepend(Publisher) to add the value issued by the second Publisher before the value of the original Publisher.
Add the following code to our playground:
/ / 1
let publisher1 = [3.4].publisher
let publisher2 = [1.2].publisher
/ / 2
publisher1
.prepend(publisher2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create two publishers. One releases the numbers 3 and 4, and the second releases the numbers 1 and 2.
- Add publisher2 to the beginning of Publisher1. Only after Publisher 2 sends.finished will Publisher 1 begin its work and issue the event.
Run playground to get the result:
1
2
3
4
Copy the code
As expected, values 1 and 2 are emitted first from publisher 2; Only then will Publisher1 issue 3 and 4. In this example, it is not obvious for publisher 2 to send the. Finished event, so we change it to the following code:
/ / 1
let publisher1 = [3.4].publisher
let publisher2 = PassthroughSubject<Int.Never> ()/ / 2
publisher1
.prepend(publisher2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
/ / 3
publisher2.send(1)
publisher2.send(2)
/ / 4
publisher2.send(completion: .finished)
Copy the code
- Create two publishers. The first emits values 3 and 4, while the second is a PassthroughSubject that can publish values dynamically.
- Add publisher publisher2 generated by PassthroughSubject before Publisher1.
- Send values 1 and 2 through publisher2.
- Let Publisher2 publish the. Finished event
We used PassthroughSubject to manually publish the data to make prepend’s capabilities more obvious
Run playground to get the result:
1
2
3
4
Copy the code
Note that if step 4 is omitted, which is publisher.send(completion:.finished), running playground will only print (1 and 2) because
- Publisher1 prepend is not complete
- Data published by Pubisher2 can be received by the Combine process
- Publisher1 will not receive publisher1 data until Prepend has executed (after receiving Publisher2. Finished)
Append series of operators
This set of operators concatenates events emitted by the publisher with other values. But in this case, you’ll use append(Output…) , Append (Sequence), and Append (Publisher) handle add-ons rather than prefixes. These operators work similarly to their precursors
append(Output…)
append(Output…) “Works like prepend, but in reverse order, adding in the back: It also accepts a list of variables of type Output that can be added to the data after the original publisher completes a.finished event
Add the following code to our playground:
/ / 1
let publisher = [1].publisher
/ / 2
publisher
.append(2.3)
.append(4)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create a publisher that emits only one value: 1.
- Append is used twice, first to append 2 and 3, then append 4.
Run playground to get the result:
1
2
3
4
Copy the code
Appends work exactly as you would expect, with each append waiting upstream to complete and then adding its own work to it. This means that upstream must complete or append will not work because Combine cannot know that the previous publisher has published all of its values.
To verify this, let’s try out the code for another playground:
/ / 1
let publisher = PassthroughSubject<Int.Never>()
publisher
.append(3.4)
.append(5)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
/ / 2
publisher.send(1)
publisher.send(2)
Copy the code
After the first few chapters, we won’t read too much into this part of the code, which I’m sure you can understand. Run playground to get the result:
1
2
Copy the code
This is because we manually published the data using PassthroughSubject without publishing.finished, so neither append will work until the publisher itself is complete
publisher.send(completion: .finished)
Copy the code
Run playground again to get the result:
1
2
3
4
5
Copy the code
This is where we want it to be. The important thing to remember is that this behavior is the same for the entire append family of operators as compared to prepend; No append occurs unless the previous publisher sends a.finished completion event.
append(Sequence)
This append takes any object that matches the sequence and appends its value after all of the original publisher’s values are emitted.
Add the following code to our playground:
/ / 1
let publisher = [1.2.3].publisher
publisher
.append([4.5]) / / 2
.append(Set([6.7])) / / 3
.append(stride(from: 8, to: 11, by: 2)) / / 4
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create a publisher that publishes 1, 2, and 3.
- Appends an array of values 4 and 5 (ordered).
- Appends a Set of values 6 and 7 (unordered).
- Attach a Strideable that takes 2 steps between 8 and 11
Run playground to get the result:
1
2
3
4
5
7
6
8
10
Copy the code
As you can see, the append execution is sequential because the previous publisher must complete before the next append execution. Note that the set of 6 and 7 May have different orders for you, because the set is unordered.
append(Publisher)
The last member of the append operator group takes Publisher as an argument.
Add the following code to our playground:
/ / 1
let publisher1 = [1.2].publisher
let publisher2 = [3.4].publisher
/ / 2
publisher1
.append(publisher2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
Copy the code
- Create two publishers, the first publishing 1 and 2, and the second publishing 3 and 4.
- Append publisher2 to Publisher1, and all values of publisher2 are appended to the end of Publisher1
Run playground to get the result:
1
2
3
4
Copy the code
I need another example of append(Publisher). Let’s look at the following code:
/ / 1
let publisher1 = PassthroughSubject"[Int].Never> ()let publisher2 = PassthroughSubject"[Int].Never> ()/ / 2
publisher1
.append(publisher2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
/ / 3
publisher2.send([3.4])
/ / 4
publisher1.send([1.2])
publisher1.send(completion: .finished)
/ / 5
publisher2.send([5.6])
/ / 6
publisher2.send(completion: .finished)
publisher2.send([7.8])
Copy the code
Instead of using a Publisher of the Sequence type, we use a PassthroughSubject that can manually publish data and complete state
- Create two
Subject
Type of publisher, yesPassthroughtSubject
release - Append publisher2 to Publisher1
- Publisher2 publishes an array manually
[3, 4]
- Publisher1 publishes an array manually
[1, 2]
And release one at the same time.finished
state - Publisher2 manually publishes an array again
[5, 6]
- Publisher2 Manual publishing
.finished
State, and then publish an array[7, 8]
Before you run the playground, you can think about the final print.
Let’s take a look at the printout
[1.2]
[5.6]
Copy the code
This is because if you want to executeappend(publisher2)
First publisher1 itself must be “finished”, so the first publisher2 issue [3, 4] is not processed. When Publisher1 publishes [1, 2] and.finished
Later,append(publisher2)
The publisher2 issue [5, 6] is appended to the end of publisher1’s Output as [1,2], [5, 6], after which publisher2 publishes.finished
State, and subsequent publisher2 releases cannot be received.