0

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?

pumpum
  • 555
  • 3
  • 5
  • 18
  • `flatMap()` can be used on `Data` instance, no? That's why then. Question about what it transforms that is another question and other test you can do. – Larme Apr 12 '21 at 13:04
  • @Larme it's rather the `flatMap` of Optional, not of `Data` – New Dev Apr 12 '21 at 13:06

2 Answers2

2

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?).

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Hi @New Dev, thanks for the explanation and share the link to the documentation about using .flatMap method on an optional. I really appreciate it. – pumpum Apr 12 '21 at 23:31
1

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 = [:]
}
Mahdi BM
  • 1,826
  • 13
  • 16
  • 1
    Hi @Mahdi BM, thanks for the explanation. I was confused with using flatMap on an optional data, and I did not know that an optional had the flatMap method. You explanation does clear my confusion. – pumpum Apr 12 '21 at 23:29
  • Basically, __anything__ could have a `map` function. You could create your own structure and make a `map` function for it as well. The thing is though, `map` in programming, even in other languages means _transformation_, so it is conventional to use `map` _only_ where you want to enable your type to be easily transformable, and not in other places. – Mahdi BM Apr 13 '21 at 01:25