flatMap Method in Action — Swift 3

Wilson Balderrama
4 min readNov 28, 2016

In this time we’re going to talk about the flatMap method.

But first let’s put in context.

Let’s say we have an array of arrays:

var arrayOfArrays = [ [1, 1], [2, 2], [3, 3] ]

and there is a challenge about multiplying by 2 its content and the result should look like this:

// [ [2, 2], [4, 4], [6, 6] ]

One approach to solve this challenge could be using the map Array's method two times like this:

// first iterating the array of arrays
arrayOfArrays.map { array in

// and second iterating
// the current array
return array.map { integer in
// multiplying its content by 2
return integer * 2
}
} // [ [2, 2], [4, 4], [6, 6] ]

Good, then there is a new challenge converting the multiplied array of arrays to only one array like this:

// [ 2, 2, 4, 4, 6, 6 ]

Fortunately Array’s structure has a method for this purpose that’s called joined, let's use it:

var multipliedByTwo = arrayOfArrays.map { array in
return array.map { integer in
return integer * 2
}
}
var flattened = Array(multipliedByTwo.joined())
print(flattened) // outputs: [ 2, 2, 4, 4, 6, 6 ]

But actually Array’s structure has already a method that does these two things at the same time, mapping and flattening, yes that’s flatMap:

var arrayOfArrays = [ [1, 1], [2, 2], [3, 3] ]var flattenedArray = arrayOfArrays.flatMap { array in
return array.map { integer in
return integer * 2
}
}
print(flattenedArray) // outputs: [ 2, 2, 4, 4, 6, 6 ]

Let’s see another example.

In fact if we were iterating an array, flatMap and map would behave similarly:

let numbers: [Int] = [1, 2, 3]let numbersMap = numbers.map { return $0 }
print(numbersMap) // [1, 2, 3]
let numbersFlatMap = numbers.flatMap { return $0 }
print(numbersFlatMap) // [1, 2, 3]

But if we had an array of optionals, there would be a big difference:

let numbers: [Int?] = [1, nil, 3]let numbersMap = numbers.map { return $0 }
print(numbersMap) // [Optional(1), nil, Optional(3)]
let numbersFlatMap = numbers.flatMap { return $0 }
print(numbersFlatMap) // [1, 3]

while map method practically doesn’t change anything on the numbersMap array result, flatMap does two things: first convert item’s type from Int? to Int and then removes the nil items on the resulting conversion numbersFlatMap, pretty neat.

Now let’s finish with a practical use of flatMap in the real world:

Let’s say we have a method called getJSON which based on a url will get us an array of dictionaries from a server:

let url = "https://jsonplaceholder.typicode.com/users"getJSON(from: url) { (dictionaries: [Dictionary<String, Any>]?) in
print(dictionaries)
// outputs:
// Optional([
// ["name": Leanne Graham, "id": 1],
// [..],
// ])

}

So we create a User structure to store this data:

struct User {  let id: Int
let name: String
let username: String
init?(dictionary: Dictionary<String: Any>) {
guard
let id = dictionary[“id”] as? Int,
let name = dictionary[“name”] as? String,
let username = dictionary[“username”] as? String
else {
return nil
}
self.id = id
self.name = name
self.username = username
}
}

A classical approach to convert the data in an array of User type could be something like this:

getJSON(from: url) { (dictionaries: [Dictionary<String, Any>]?) in
if let dictionaries = dictionaries {
var users = [User]()
for dictionary in dictionaries {
let user = User(dictionary: dictionary)

if let user = user {
users.append(user)
}
}
print(users)
// [ User(id: 1, name: "Leanne Graham", username: "Bret"), .. ]
}
}

Bear in mind that when are creating a new User passing adictionary, it is possible that it could fail the initializaton and return a nil since we declared the init as a failable one.

struct User {
// some code
// failable initalizer
init?(dictionary: Dictionary<String: Any>) {
// some code
}
}

That’s why we verify that user is not nil before adding to the users array.

But there is another approach iterating the dictionaries using flatMap:

getJSON(from: url) { (dictionaries: [Dictionary<String, Any>]?) in
if let dictionaries = dictionaries {
var users = [User]()
// Accepts a dictionary and return a User?
let closure: (Dictionary<String, Any>) -> User?
// Initializing the closure
closure = { dictionary in
return User(dictionary: dictionary)
}
users = dictionaries.flatMap(closure) print(users)
// [ User(id: 1, name: "Leanne Graham", username: "Bret"), .. ]
}
}

So flatMap will do three things:

  1. Of course iterate the dictionaries array to convert an array of User type
  2. if a User is created without any issue , flatMap converts from User? to User type every element.
  3. it filters the nil values because failable initializers (init?) could return nil

AfterflatMap finishes, we have an array of [User].

To finish we can reduce the code even more, like this:

getJSON(from: url) { (dictionaries: [Dictionary<String, Any>]?) in
if let dictionaries = dictionaries {
var users = [User]()
users = dictionaries.flatMap(User.init) print(users)
// [ User(id: 1, name: "Leanne Graham", username: "Bret"), .. ]
}
}

if you see the code where we used a closure, its type was this:

let closure: (Dictionary<String, Any>) -> User?// codeusers = dictionaries.flatMap(closure)

Expects a Dictionary<String, Any> as an argument and returns a User?

And if you see the initializer function of the User struct:

init?(dictionary: Dictionary<String: Any>) {
// some code
}

Expect a Dictionary<String, Any> as an argument and of course itsinit? function call will return a User?

So that’s why we passed User.init (which refers to User structure’s init function) to flatMap:

users = dictionaries.flatMap(User.init)

We saw various examples of using flatMap method but some important points to remember are:

  • when working with array of arrays, applying flatMap will do two things, mapping and flattening.
  • when working with arrays of optional types, flatMap will map, convert the optional types to non optional, and remove the nil values.

And that’s it, hope you find this article helpful.

Thanks for reading me!

--

--

Wilson Balderrama

I like to run everyday while listening podcasts or audiobooks, read/write at Medium and of course I try to improve my skills as an iOS Developer everyday.