Why can we chain flatMap to UserDefaults.standard.data

38 Views Asked by At

In an article about UserDefaults in iOS development, I saw a code snippet where flatMap is chained to UserDefaults.standard.data like below:

self.isReadStatuses = UserDefaults.standard.data(forKey: "isReadStatuses")
      .flatMap { try? JSONDecoder().decode([URL: Bool].self, from: $0) } ?? [:]

Does anyone know Why can we use .flatMap here?

2

There are 2 best solutions below

1
New Dev On BEST ANSWER

Because UserDefaults.standard.data(forKey:) returns Data? - an Optional<Data>, and Optional has a .flatMap method.

Specifically here, the flatMap closure gets a non-optional Data, and attempts to decode it returning another [URL:Bool]? (also, an optional because of try?).

2
Mahdi BM On

I can guess why are you confused, although im not sure. I think you think that .map (and it's brothers, .flatMap and .compactMap) can only be used on Collections (e.g. an Array). That is completely wrong. .map (and the other 2) have a meaning of transformation, not iterating through an collection/array. So while they can be used on arrays, they have many more use-cases as well.
You can read more about differences between those 3 kinds of map here.
In the code you showed, the author of that blog post has used .flatMap with intention of transforming an Optional<Data> value (aka Data?) to [URL: Bool] which is the value he wants.

let udData = UserDefaults.standard.data(forKey: "isReadStatuses")

// Short way:
let isReadStatuses1 = udData.flatMap {
    try? JSONDecoder().decode([URL: Bool].self, from: $0)
} ?? [:]

// Long way:
let isReadStatuses2: [URL: Bool]
if let data = udData {
    isReadStatuses2 = (try? JSONDecoder().decode([URL: Bool].self, from: data)) ?? [:]
} else {
    isReadStatuses2 = [:]
}