[RxSwift] flatMapの呼び出しを恐がりたくない人生だった

`flatMap`の呼び出しが理解不能だったプログラマの記録です。(ビギナー向け)
この記事だけで`flatMap`の挙動が把握できるようにはならないのですが、
私のような悩める人に対し、違った角度からの見方を提供できたならば僥倖です...。

## 環境

Xcode 10.1(10B61)
Swift 4.2.1
RxSwift 4.4.0

## flatMapの呼び出しが理解不能だった

`map`のほうはまだやってることわかるんですよ。
ただ、`flatMap`となると...あいつなんなの？
ただでさえオペレータのメソッドチェーンを追ってるだけでも脳メモリに負荷がかかっているのに、
引数のクロージャの中で、なぜいきなり仰々しい構文が途中で入ってくるのか...こわい...

```swift:  
let result: Observable<Bool> = userNameTextField.rx.text
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()                            // ここまで1つのObservableの話をしていたと思っていたのに...
    .flatMap { userName in
        return userNameValidator.validate(text: text)  // なんかいきなり別のObservableの話が登場してきてこわい
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()
```

1つの`Observable`の話をしていたと思いきや、
新しい`Observable`の話が食い込んでくることの**？？？感**といったらありませんでした。

この記事を書くことで、自らに巣食っているこの恐怖を少しでも克服していきたいと思います。

## 恐さを克服する

まず、`map`も`flatMap`もオペレーターの一種であることに変わりはなく、
「新しいObservableを返す」という点で目的は共通していることがわかると、いくばくか落ち着きを取り戻せます。
私が大好きないつものやつ**「関数のシグネチャを確認」**をここで早くも投入してみましょう。
[mapの定義](https://github.com/ReactiveX/RxSwift/blob/65efb404c80fe1b23108c8fbb57d05b615e21c93/RxSwift/Observables/Map.swift#L20-L21)と[flatMapの定義](https://github.com/ReactiveX/RxSwift/blob/65efb404c80fe1b23108c8fbb57d05b615e21c93/RxSwift/Observables/Merge.swift#L19-L20)を確認してみます。

```swift:map
public func map<R>(_ transform: @escaping (E) throws -> R) -> Observable<O.E>
```

```swift:flatMap
public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>
```

`map`と`flatMap`双方、返り値は`Observable<O.E>`で共通しています。
`flatMap`といえど、RxSwiftの数多あるオペレータの一種であることに変わりはないことが、いまいちど確認できました。

そして、`flatMap`が引数として取るクロージャが遵守すべき型の約束はどうなっているでしょうか？

```swift:flatMap(再掲)
public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>
```

1. 型パラメータとその型制約```<O: ObservableConvertibleType>```  
と、
2. 引数としてのクロージャ(selector)の返り値の型`O`

に着目ください。
この2点を総合すると、**「クロージャ(selector)は返り値としてObservable(ConvertibleType)を返さないとだめだよ！」**という指定がなされていることがわかります。

つまり、このようなコードは書けません。

```swift
let result: Observable<String> = Observable.of("梅", "竹", "松")
    .flatMap { text -> String in  // 返り値はString(= Observable(ConvertibleType)ではない)から🙅‍♀️
        return "\(text)☆"
    }
```

クロージャの内部に書かれる処理が得てして複雑になるのも、
`Observable(ConvertibleType)`を作って返さないといけないのだから、ある意味当然だったというわけですね...。

この際ですので、ついでに`map`が引数として取るクロージャのシグネチャも見てみましょう。

```swift:map
public func map<R>(_ transform: @escaping (E) throws -> R) -> Observable<O.E>
```

引数としてのクロージャ(transform)の返り値の型`R`を見て分かる通り、
「`Observable(ConvertibleType)`を返してね！」という`flatMap`のときとは対照的に、クロージャの返り値の型に制約は設けられていないことが確認できます。
先ほどの`flatMap`の例では書けなかったコードも、`map`であれば当然のようにOKです。

```swift:
let result: Observable<String> = Observable.of("梅", "竹", "松")
    .map { text -> String in  // 🙆‍♀️
        return "\(text)☆"
    }
```

## Observableをreturnする形で書けると何が嬉しいのか

自身や、自身が保有するモデルクラスが持つObservableを返すメソッドを呼び出すことで、
`flatMap`が引数に取るクロージャの要件を満たすことができます。
冒頭の例を取るなら...

```swift:こんなバリデーションを担うクラスがあったとして...
struct UserNameValidator {
    func validate(userName: String) -> Observable<Bool> {
        if ... /* なんらかのバリデーションロジック */ {
            return .just(false)  // バリデーション失敗
        } else {
            return .just(true)   // バリデーション成功
        }
    }
}
```

```swift:flatMapのクロージャ内で使用する
let validator = UserNameValidator()

userNameTextField.rx.text
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()
    .flatMap { userName in
        return validator.validate(text: text)  // 自身が保有するモデルクラスのメソッド呼び出し
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()                                   // もちろん後続でチェーンもできる
```

バリデーションを担うクラスのメソッドを呼び出す形で`flatMap`が引数として取るクロージャをしっかり充足させ、`flatMap`を呼んています。
こうした形を取ることで、責務を適切に分割した設計に近づけやすくなる可能性などが考えられます。

逆に言えば、モデルクラスはObservableを返すようにメソッドを定義してあげると、
その利用者側は後続のオペレータにチェーンしてもらえたりなど扱いやすくなる、といったメリットが考えられます。
(iOSアプリ設計パターン入門 第7章 MVVM〜Modelの項に記載されていました。)

筆者が知覚しているメリットはこれだけですが、他にもたくさんありそうです。

## 同じことは`map`を使っても書けるのでは？

書けます。**結果の型がネストしてもいいのであれば**。

冒頭の例において、`flatMap`を`map`に書き換えてみると...

```swift
let result: Observable<Observable<Bool>> = usernameTextField.rx.text  // 型がネストしてしまった🤣
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()
    .map { [unowned self] text in  // なぜならここをflatMapからmapに変えてしまったからね...
        return self.validate(text: text)
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()
```

`Observable`が`Observable`でネストしてしまう結果になってしまいました...。

この挙動は、
↓のように、**`String`を返すクロージャを取る`map`の結果 => `Observable<String>`** になる、のだから...

```swift
let result: Observable<String> = Observable.of("梅", "竹", "松")
    .map { text -> String in
        return "\(text)☆"
    }
```

↓のように、**`Observable<String>`を返すクロージャを取る`map`の結果 => `Observable<Observable<String>`** になる、というわけですね。

```swift
let result: Observable<Observable<String>> = Observable.of("梅", "竹", "松")
    .map { text -> Observable<String> in
        return Observable.just("\(text)☆")
    }
```

そしてこのネストされたObservableを購読したいとなると、目もあてられない感じになってしまいます。

```swift:こんなコード書きたいと思う人はいない...🤮🤮
result.subscribe(onNext: { innerObservable in
    innerObservable.subscribe(onNext: { text in
        print(text)
    })
    .disposed(by: self.bag)
})
.disposed(by: bag)
```

`flatMap`を使えば、Observableでネストすることなく結果を返してくれます。

```swift
let result: Observable<String> = Observable.of("梅", "竹", "松")
    .flatMap { text -> Observable<String> in
        return Observable.just("\(text)☆")
    }
```

## もっとflatMapと仲良くなりたいな

この記事では一切触れなかった切り口である

> 元の Observable のイベントを Observable に変換した上で、その発行するイベントをマージします。

という点に言及している[こちら](https://qiita.com/k5n/items/e80ab6bff4bbb170122d#flatmap)などを参考にさせてもらっています。
他にも[こちら](https://qiita.com/t-ae/items/266a8372abf65ab1986b)にあるような、

> flatMapやflatMapLatestはクロージャ内で生成されたオブザーバブルにCompletedが流れても、それをまとめた出力にはCompletedが流れない

という点も見逃せないですね。
これを知ってから[マーブル図](http://reactivex.io/documentation/operators/flatmap.html)を見直すと、確かに`flatMap`のクロージャに書かれている`onCompleted`(縦線)が、合成後のストリーム上では流れて(書かれて)いないことに気づけました...。

## おわりに

初めて筆者が`RxSwift`に触れてからすでに1年半を数えますが、
`flatMap`だけが、というより`RxSwift`自体、全然自信が湧かないのです。難しいよ...😂
どうかこの記事が私のような悩める人に対し、違った角度からの見方を少しでも提供できていたら...と願っています🙏

## 参考書籍

・モバイルアプリ開発エキスパート養成読本
・RxSwift研究読本 Ⅰ
・iOSアプリ設計パターン入門