There is an updated edition of this book available! View Latest Edition

# 3 Transforming Operators Written by Scott Gardner

Having completed section 1, you’ve already learned a lot. You should feel pretty good about that accomplishment! You’ve laid a solid foundation on the fundamentals of Combine, and now you’re ready to build upon it.

In this chapter, you’re going to learn about one of the essential categories of operators in Combine: Transforming operators. You’ll use transforming operators all the time, to manipulate values coming from publishers into a format that is usable for your subscribers. As you’ll see, there are parallels between transforming operators in Combine and regular operators in the Swift standard library, such as `map` and `flatMap`.

By the end of this chapter, you’ll be transforming all the things!

## Getting started

Open the starter playground for this chapter, which already has Combine imported and is ready to go.

### Operators are publishers

In Combine, methods that perform an operation on values coming from a publisher are called operators.

Each Combine operator actually returns a publisher. Generally speaking, that publisher receives the upstream values, manipulates the data, and then sends that data downstream. To streamline things conceptually, the focus will be on using the operator and working with its output. Unless an operator’s purpose is to handle errors, if it receives an error from an upstream publisher, it will just publish that error downstream.

Note: You’ll focus on transforming operators in this chapter, so error handling will not appear in each operator example. You’ll learn all about error handling in Chapter 16, “Error Handling.”

## Collecting values

Publishers can emit individual values or collections of values. You’ll frequently want to work with collections, such as when you want to populate a list of views. You’ll learn how to do this later in the book.

### `collect()`

The `collect` operator provides a convenient way to transform a stream of individual values from a publisher into an array of those values. To help understand how this and all other operators you’ll learn about in this book, you’ll use marble diagrams.

Marble diagrams help to visualize how operators work. The top line is the upstream publisher. The box represents the operator. And the bottom line is the subscriber, or more specifically, what the subscriber will receive after the operator manipulates the values coming from the upstream publisher.

The bottom line could also be another operator that receives the output from the upstream publisher, performs its operation, and sends those values downstream.

As depicted in this marble diagram, `collect` will buffer a stream of individual values into an array of those values once the upstream publisher completes. It will then emit that array downstream.

``````example(of: "collect") {
["A", "B", "C", "D", "E"].publisher
.store(in: &subscriptions)
}
``````

This is not using the `collect` operator yet. Run the playground, and you’ll see each value is emitted and printed individually followed by the completion:

``````——— Example of: collect ———
A
B
C
D
E
finished
``````

Now insert the use of `collect` before the `sink`. Your code should look like this:

``````["A", "B", "C", "D", "E"].publisher
.collect()
.store(in: &subscriptions)
``````

Run the playground, and now the sink receives one emitted collection followed by the completion event:

``````——— Example of: collect ———
["A", "B", "C", "D", "E"]
finished
``````

Note: Be careful when working with `collect()` and other buffering operators that do not require specifying a count or limit. They will use an unbounded amount of memory to store received values.

There are a few variations of the `collect` operator. For example, you can specify that you only want to receive up to a certain number of values.

Replace the following line:

``````.collect()
``````

With:

``````.collect(2)
``````

Run the playground, and you’ll see the following output:

``````——— Example of: collect ———
["A", "B"]
["C", "D"]
["E"]
finished
``````

The last value, `E`, is emitted as an array. That’s because the upstream publisher completed before `collect` filled its prescribed buffer, so it sent whatever it had left as an array.

## Mapping values

In addition to collecting values, you’ll often want to transform those values in some way. Combine offers several mapping operators for that purpose.

### `map(_:)`

The first you’ll learn about is `map`, which works just like Swift’s standard `map`, except that it operates on values emitted from a publisher. In the marble diagram, `map` takes a closure that multiplies each value by `2`.

``````example(of: "map") {
// 1
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut

// 2
[123, 4, 56].publisher
// 3
.map {
formatter.string(for: NSNumber(integerLiteral: \$0)) ?? ""
}
.store(in: &subscriptions)
}
``````

Here’s the play-by-play:

1. Create a number formatter to spell out each number.
2. Create a publisher of integers.
3. Use `map`, passing a closure that gets upstream values and returns the result of using the formatter to return the number’s spelled out string.

Run the playground, and you will see this output:

``````——— Example of: map ———
one hundred twenty-three
four
fifty-six
``````

### Map key paths

The `map` family of operators also includes three versions that can map into one, two, or three properties of a value using key paths. Their signatures are as follows:

• `map<T>(_:)`
• `map<T0, T1>(_:_:)`
• `map<T0, T1, T2>(_:_:_:)`

The `T` represents the type of values found at the given key paths.

In the next example, you’ll use the `Coordinate` type and `quadrantOf(x:y:)` method defined in Sources/SupportCode.swift. `Coordinate` has two properties: `x` and `y`. `quadrantOf(x:y:)` takes x and y values as parameters and returns a string indicating the quadrant for the x and y values.

Feel free to review these definitions if you’re interested, and then add the following example to your playground:

Add this example to see how you’d use `map(_:_:)` to map into two key paths:

``````example(of: "map key paths") {
// 1
let publisher = PassthroughSubject<Coordinate, Never>()

// 2
publisher
// 3
.map(\.x, \.y)
// 4
print(
"The coordinate at (\(x), \(y)) is in quadrant",
)
})
.store(in: &subscriptions)

// 5
publisher.send(Coordinate(x: 10, y: -8))
publisher.send(Coordinate(x: 0, y: 5))
}
``````

In this example you’re using the version of `map` that maps into two properties via key paths.

Step-by-step, you:

1. Create a publisher of `Coordinate`s that will never emit an error.
2. Begin a subscription to the publisher.
3. Map into the `x` and `y` properties of `Coordinate` using their key paths.
4. Print a statement that indicates the quadrant of the provide `x` and `y` values.
5. Send some coordinates through the publisher.

Run the playground and the output from this subscription will be the following:

``````——— Example of: map key paths ———
The coordinate at (10, -8) is in quadrant 4
The coordinate at (0, 5) is in quadrant boundary
``````

### `tryMap(_:)`

Several operators, including `map`, have a counterpart `try` operator that will take a closure that can throw an error. If you throw an error, it will emit that error downstream. Add this example to the playground:

``````example(of: "tryMap") {
// 1
Just("Directory name that does not exist")
// 2
.tryMap { try FileManager.default.contentsOfDirectory(atPath: \$0) }
// 3
.store(in: &subscriptions)
}
``````

Here’s what you just did, or at least tried to do!

1. Create a publisher of a string representing a directory name that does not exist.
2. Use `tryMap` to attempt to get the contents of that nonexistent directory.
3. Receive and print out any values or completion events.

Notice that you still need to use the `try` keyword when calling a throwing method.

Run the playground and observe that `tryMap` outputs a failure completion event with the appropriate “folder doesn’t exist” error (output abbreviated):

``````——— Example of: tryMap ———
failure(..."The folder “Directory name that does not exist” doesn't exist."...)
``````

## Flattening publishers

This section’s title might not shed any light on what you’re about to learn, unless you have some prior experience with reactive programming. However, by the end of this section, everything will be illuminated for you.

### `flatMap(maxPublishers:_:)`

The `flatMap` operator can be used to flatten multiple upstream publishers into a single downstream publisher — or more specifically, flatten the emissions from those publishers.

The publisher returned by `flatMap` does not — and often will not — be of the same type as the upstream publishers it receives.

A common use case for `flatMap` in Combine is when you want to pass elements emitted by one publisher to a method that itself returns a publisher, and ultimately subscribe to the elements emitted by that second publisher.

Time to implement an example to see this in action. Add this new example:

``````example(of: "flatMap") {
// 1
func decode(_ codes: [Int]) -> AnyPublisher<String, Never> {
// 2
Just(
codes
.compactMap { code in
guard (32...255).contains(code) else { return nil }
return String(UnicodeScalar(code) ?? " ")
}
// 3
.joined()
)
// 4
.eraseToAnyPublisher()
}
}
``````

From the top, you:

1. Define a function that takes an array of integers, each representing an ASCII code, and returns a type-erased publisher of strings that never emits errors.
2. Create a `Just` publisher that converts the character code into a string if it’s within the range of 0.255, which includes standard and extended printable ASCII characters.
3. Join the strings together.
4. Type erase the publisher to match the return type for the fuction.

With that handiwork completed, add this code to your example to put that function and the `flatMap` operator to work:

``````// 5
[72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
.publisher
.collect()
// 6
.flatMap(decode)
// 7
.store(in: &subscriptions)
``````

With this code, you: 5. Create a secret message as an array of ASCII character codes, convert it to a publisher, and collect its emitted elements into a single array. 6. Use `flatMap` to pass the array element to your decoder function. 7. Subscribe to the elements emitted by the pubisher returned by `decode(_:)` and print out the values.

Run the playground, and you’ll see the following:

``````——— Example of: flatMap ———
Hello, World!
``````

Recall the definition from earlier: `flatMap` flattens the output from all received publishers into a single publisher. This can pose a memory concern, because it will buffer as many publishers as you send it to update the single publisher it emits downstream.

To understand how to manage this, take a look at this marble diagram of `flatMap`:

In the diagram, `flatMap` receives three publishers: `P1`, `P2`, and `P3`. Each of these publishers has a `value` property that is also a publisher. `flatMap` emits the `value` publishers’ values from `P1` and `P2`, but ignores `P3` because `maxPublishers` is set to `2`. You’ll get more practice working with `flatMap` and its `maxPublishers` parameter in Chapter 19, “Testing.”

You now have a handle on one of the most powerful operators in Combine. However, `flatMap` is not the only way to swap input with a different output. So, before wrapping up this chapter, you’ll learn a couple more useful operating for doing the ol’ switcheroo.

## Replacing upstream output

Earlier in the `map` example, you worked with `Foundation`’s `Formatter.string(for:)` method. It produces an optional string, and you used the nil-coalescing operator (`??`) to replace a `nil` value with a non-`nil` value. Combine also includes an operator that you can use when you want to always deliver a value.

### `replaceNil(with:)`

As depicted in the following marble diagram, `replaceNil` will receive optional values and replace `nil`s with the value you specify:

``````example(of: "replaceNil") {
// 1
["A", nil, "C"].publisher
.eraseToAnyPublisher()
.replaceNil(with: "-") // 2
.sink(receiveValue: { print(\$0) }) // 3
.store(in: &subscriptions)
}
``````

What you just did:

1. Create a publisher from an array of optional strings.
2. Use `replaceNil(with:)` to replace `nil` values received from the upstream publisher with a new non-`nil` value.
3. Print out the value.

Note: There is an issue which causes the wrong overload of `replaceNil(with:)` to be used. This results in the type remaining as `Optional<String>` instead of being fully unwrapped. The `eraseToAnyPublisher()` in the code is used to go around that bug. You can learn more about this issue in the Swift forums: https://bit.ly/30M5Qv7

Run the playground, and you will see the following:

``````——— Example of: replaceNil ———
A
-
C
``````

There is a subtle but important difference between using the nil-coalescing operator `??` and `replaceNil`. The `??` operator can still result in an `nil` result, while `replaceNil` cannot. Change the usage of `replaceNil` to the following, and you will get an error that the optional must be unwrapped:

``````.replaceNil(with: "-" as String?)
``````

Revert that change before moving on. This example also demonstrates how you can chain together multiple operators in a compositional way. This allows you to manipulate the values coming from the origin publisher to the subscriber in a wide variety of ways.

### `replaceEmpty(with:)`

You can use the `replaceEmpty(with:)` operator to replace — or really, insert — a value if a publisher completes without emitting a value.

In the following marble diagram, the publisher completes without emitting anything, and at that point the `replaceEmpty(with:)` operator inserts a value and publishes it downstream:

Add this new example to see it in action:

``````example(of: "replaceEmpty(with:)") {
// 1
let empty = Empty<Int, Never>()

// 2
empty
.store(in: &subscriptions)
}
``````

What you’re doing here:

1. Create an empty publisher that immediately emits a completion event.
2. Subscribe to it, and print received events.

The `Empty` publisher type can be used to create a publisher that immediately emits a `.finished` completion event. It can also be configured to never emit anything by passing `false` to its `completeImmediately` parameter, which is `true` by default. This publisher is useful for demo or testing purposes, or when all you want to do is signal completion of some task to a subscriber. Run the playground and its completion event is printed:

``````——— Example of: replaceEmpty ———
finished
``````

Now, insert this line of code before calling `sink`:

``````.replaceEmpty(with: 1)
``````

Run the playground again, and this time you get a `1` before the completion:

``````1
finished
``````

## Incrementally transforming output

You’ve seen how Combine includes operators such as `map` that correspond and work similarly to higher-order functions found in the Swift standard library. However, Combine has a few more tricks up its sleeve that let you manipulate values received from an upstream publisher.

### `scan(_:_:)`

A great example of this in the transforming category is `scan`. It will provide the current value emitted by an upstream publisher to a closure, along with the last value returned by that closure.

In the following marble diagram, `scan` begins by storing a starting value of `0`. As it receives each value from the publisher, it adds it to the previously stored value, and then stores and emits the result:

Note: If you are using the full project to enter and run this code, there’s no straightforward way to plot the output — as is possible in a playground. Instead, you can print the output by changing the `sink` code in the example below to `.sink(receiveValue: { print(\$0) })`.

For a practical example of how to use `scan`, add this new example to your playground:

``````example(of: "scan") {
// 1
var dailyGainLoss: Int { .random(in: -10...10) }

// 2
let august2019 = (0..<22)
.map { _ in dailyGainLoss }
.publisher

// 3
august2019
.scan(50) { latest, current in
max(0, latest + current)
}
.store(in: &subscriptions)
}
``````

In this example, you:

1. Create a computed property that generates a random integer between `-10` and `10`.
2. Use that generator to create a publisher from an array of random integers representing fictitious daily stock price changes for a month.
3. Use `scan` with a starting value of `50`, and then add each daily change to the running stock price. The use of `max` keeps the price non-negative — thankfully stock prices can’t fall below zero!

This time, you did not print anything in the subscription. Run the playground, and then click the square Show Results button in the right results sidebar.

There’s also an error-throwing `tryScan` operator that works similarly. If the closure throws an error, `tryScan` fails with that error.

## Challenge

Practice makes permanent. Complete this challenge to ensure you’re good to go with transforming operators before moving on.

### Challenge: Create a phone number lookup using transforming operators

Your goal for this challenge is to create a publisher that does two things:

1. Receives a string of ten numbers or letters.
2. Looks up that number in a contacts data structure.

The starter playground, which can be found in the challenge folder, includes a `contacts` dictionary and three functions. You’ll need to create a subscription to the `input` publisher using transforming operators and those functions. Insert your code right below the `Add your code here` placeholder, before the `forEach` blocks that will test your implementation.

Tip: A function or closure can be passed directly to an operator as a parameter if the function signature matches. For example, `map(convert)`.

Breaking down this challenge, you’ll need to:

1. Convert the input to numbers — use the `convert` function, which will return `nil` if it cannot convert the input to an integer.
2. If `nil` was returned from the previous operator, replace it with a `0`.
3. Collect ten values at a time, which correspond to the three-digit area code and seven-digit phone number format used in the United States.
4. Format the collected string value to match the format of the phone numbers in the contacts dictionary — use the provided `format` function.
5. “Dial” the input received from the previous operator — use the provided `dial` function.

#### Solution

Did your code produce the expected results? Starting with a subscription to `input`, first you needed to convert the string input one character at a time into integers:

``````input
.map(convert)
``````

Next you needed to replace `nil` values returned from `convert` with `0`s:

``````.replaceNil(with: 0)
``````

To look up the result of the previous operations, you needed to collect those values, and then format them to match the phone number format used in the `contacts` dictionary:

``````.collect(10)
.map(format)
``````

Finally, you needed to use the `dial` function to look up the formatted string input, and then subscribe:

``````.map(dial)
``````

Running the playground will produce the following:

``````——— Example of: Create a phone number lookup ———
Dialing Marin (408-555-4321)...
Dialing Shai (212-555-3434)...
``````

Bonus points if you hook this up to a VoIP service!

## Key points

• Methods that perform operations on output from publishers are called operators.
• Operators are also publishers.
• Transforming operators convert input from an upstream publisher into output that is suitable for use downstream.
• Marble diagrams are a great way to visualize how each Combine operators work.
• Be careful when using any operators that buffer values such as `collect` or `flatMap` to avoid memory problems.
• Be mindful when applying existing knowledge of functions from Swift standard library. Some similarly-named Combine operators work the same while others work entirely differently.
• Multiple operators can be chained together in a subscription.

## Where to go from here?

Way to go! You just transformed yourself into a transforming titan.

Now it’s time to learn how to use another essential collection of operators to filter what you get from an upstream publisher.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.