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

# 3 Transforming Operators Written by Scott Gardner

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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.

## 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.

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

## 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)
}
``````
``````——— 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:

``````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))
}
``````
``````——— 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)
}
``````
``````——— 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.

``````public struct Chatter {
public let name: String
public let message: CurrentValueSubject<String, Never>

public init(name: String, message: String) {
self.name = name
self.message = CurrentValueSubject(message)
}
}
``````
``````example(of: "flatMap") {
// 1
let charlotte = Chatter(name: "Charlotte", message: "Hi, I'm Charlotte!")
let james = Chatter(name: "James", message: "Hi, I'm James!")

// 2
let chat = CurrentValueSubject<Chatter, Never>(charlotte)

// 3
chat
.store(in: &subscriptions)
}
``````
``````——— Example of: flatMap ———
Charlotte wrote: Hi, I'm Charlotte!
``````
``````// 4
charlotte.message.value = "Charlotte: How's it going?"

// 5
chat.value = james
``````
``````Charlotte wrote: Hi, I'm Charlotte!
James wrote: Hi, I'm James!
``````
``````chat
.store(in: &subscriptions)
``````
``````chat
// 6
.flatMap { \$0.message }
// 7
.store(in: &subscriptions)
``````
``````Hi, I'm Charlotte!
Charlotte: How's it going?
Hi, I'm James!
``````
``````james.message.value = "James: Doing great. You?"
charlotte.message.value = "Charlotte: I'm doing fine thanks."
``````
``````James: Doing great. You?
Charlotte: I'm doing fine thanks.
``````
``````.flatMap { \$0.message }
``````
``````.flatMap(maxPublishers: .max(2)) { \$0.message }
``````

``````// 8
let morgan = Chatter(name: "Morgan",
message: "Hey guys, what are you up to?")

// 9
chat.value = morgan

// 10
charlotte.message.value = "Did you hear something?"
``````
``````——— Example of: flatMap ———
Hi, I'm Charlotte!
Charlotte: How's it going?
Hi, I'm James!
James: Doing great. You?
Charlotte: I'm doing fine thanks.
Did you hear something?
``````

## 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
.replaceNil(with: "-") // 2
.sink(receiveValue: { print(\$0) }) // 3
.store(in: &subscriptions)
}
``````
``````——— Example of: replaceNil ———
Optional("A")
Optional("-")
Optional("C")
``````
``````["A", nil, "C"].publisher
.replaceNil(with: "-")
.map { \$0! }
.store(in: &subscriptions)
``````
``````A
-
C
``````
``````.replaceNil(with: "-" as String?)
``````

### `replaceEmpty(with:)`

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

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

// 2
empty
.store(in: &subscriptions)
}
``````
``````——— Example of: replaceEmpty ———
finished
``````
``````.replaceEmpty(with: 1)
``````
``````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.

``````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)
}
``````

## 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:

#### 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)
``````
``````.replaceNil(with: 0)
``````
``````.collect(10)
.map(format)
``````
``````.map(dial)
``````
``````——— Example of: Create a phone number lookup ———
Dialing Marin (408-555-4321)...
Dialing Shai (212-555-3434)...
``````

## 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.

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.