Transforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatest
map
, flatMap
and switchToLatest
are the most important transforming operators of the Swift Combine Framework. Let’s learn how they work, what is the difference between them and when to use what.
If you want to freshen the basics, here is the bird-eye overview of the Combine Framework.
Transforming Elements with Map
Combine’s map(_:)
operator works just like map
from the Swift standard library, except it operates with publishers. It takes a closure that changes an element into another element:
[1, 2, 3]
.publisher
.map { $0 * 2 }
.sink { print($0) }
It will print:
2
4
6
Transforming Publishers with FlatMap
The flatMap(maxPublishers:_:)
operator transforms a publisher into completely new publisher that produces elements of the same type. It is used when you want to reach into inner publisher to get its elements.
To better understand the operator, let’s begin with an example. Say, we have a User
struct with name
property as a publisher:
struct User {
let name: CurrentValueSubject<String, Never>
}
To print the names of the stream of users we create PassthroughSubject
and send User
objects to it:
let userSubject = PassthroughSubject<User, Never>()
userSubject
.map { $0.name } // 🛑 Oops, compilation error here
.sink { print($0) }
let user = User(name: .init("User 1"))
userSubject.send(user)
It will fail with compilation error, since userSubject
is a publisher of publishers. The flatMap
operator allows to overcome this and reach name
values:
userSubject
.flatMap { $0.name }
.sink { print($0) }
It will print:
User 1
Controlling Number of Publishers with FlatMap
flatMap
has maxPublishers
parameter, that controls how many publishers the method can accept. It defaults to unlimited:
let anotherUser = User(name: .init("AnotherUser 1"))
userSubject.send(anotherUser)
anotherUser.name.send("AnotherUser 2")
user.name.send("User 2")
It will print names of both user
and anotherUser
:
User 1
AnotherUser 1
AnotherUser 2
User 2
Once we set maxPublishers
to 1
, flatMap
accepts only user
and ignores anotherUser
:
userSubject
.flatMap(maxPublishers: .max(1)) { $0.name }
.sink { print($0) }
let user = User(name: .init("User 1"))
userSubject.send(user)
let anotherUser = User(name: .init("AnotherUser 1"))
userSubject.send(anotherUser)
anotherUser.name.send("AnotherUser 2")
user.name.send("User 2")
It will print:
User 1
User 2
Switching to Latest Publisher
As we’ve seen with flatMap
, it’s very common to have a publisher of publishers. Say, firing a network request per button tap already creates such a stream. When requests are fired in a short period of time, their responses may arrive out-of-order. However, we are almost always interested in the latest network request. It appears that Combine has an operator that achieves exactly such behavior: switchToLatest()
.
Continuing with our example, let’s see how we can switch the stream to the latest publisher, i.e. anotherUser
. The only part that changes is the subscription:
userSubject
.map { $0.name }
.switchToLatest()
.sink { print($0) }
It will print:
User 1
AnotherUser 1
AnotherUser 2
Once anotherUser
is inserted into the stream, userSubject
automatically switches to it and no longer propagates values from user
.
💡 If you have RxSwift or RxJava background, this is what
flatMapLatest()
does. In Combine it translates intomap
+switchToLatest
.
Further Reading
If you enjoyed this article, I’ve been writing a lot about the Swift Combine Framework:
Thanks for reading!
If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.