Combine の operator を自作してみよう

Combine の登場により、Swift でようやく純正のリアクティブ[^reactive]ライブラリができて、RxSwift や ReactiveSwift などの外部ライブラリに依存しなければならない時代が終わろうとしています。筆者はこれまで RxSwift をどうしても好きにはなれませんでしたが、Combine は割と好きです。心の底から「ファーストパーティ製」に対するバイアスを抜きに、個人的に Combine が一番気に入ってる部分であり、Combineの大きな特徴の一つとして、全ての operator が `protocol Publisher` に準拠した独立の型になっており、operator を繋いでいくと、利用する側がこれまでどんな operator が繋がれたか、元がどんな publisher か、といったことを意識させないために、作り手に `eraseToAnyPublisher()` による暴露が強要しています[^combine-publishers]。これによって、これまで RxSwift のアンチパターンとも言えるよくある間違いの一つ、購読する側にそのまま `BehaviorRelay` として暴露してしまう問題[^rx-publisher]が仕組み上ほぼ不可能にしました。

ところが、そのため逆に operator を自作するのも、RxSwift より難しいというか、そもそもどう作ればいいのかイメージが全く湧かない人も多いかと思います。この記事はそんなあなたに、operator の自作の仕方を解説したいと思います。今回は例として、2019 年の WWDC で公開した [Combine in Practice](https://developer.apple.com/videos/play/wwdc2019/721/) に出た「API からデータを受け取って、それを自分の型にデコードして次に流す；もしデコード失敗したら代わりのデフォルト値を流す；ただしデコード失敗しても購読を終了させない」という組み合わせ技を、自作の operator にしてみます。

まずは動画のおおよそ 10 分のところを見てもらうとイメージが掴めるかと思います。API から `Data` が流されてきたら、まず普通に `decode` してしまうと、万が一デコード失敗したら、そこで代わりのデフォルト値を流しても、購読自体はデコードエラーによって終了してしまうので、それ以降の値が一切取れなくなります。ですのでそれを避けたい場合、まずは `flatMap` して、その中身で `Just` を出してから `decode` します。そうすればデコードエラーになったとしても終了するのは最初の `Just` だけで済むので、`flatMap` によって作られた `Publisher` の購読は終わりません。コードにするとこんな感じです：

```swift
somePublisher
    .flatMap({ value in
        Just(value)
            .decode(type: Output.self, decoder: decoder)
            .catch { _ in
                Just(defaultValue)
            }
    })
    // .sink　や .assign などでデコードした値で何かする
```

さて、このように `flatMap` を書くともちろん目的は達成しますが、やはりいちいち書くの面倒ですので、これをまとめて一つの operator にしたいですね。

Combine の operator は全て `Publishers` の中で定義されているので、それに合わせて同じく `Publishers` の中に operator の型を作りましょう。型名は `RecoverableDecode` にしましょう：

```swift
extension Publishers {
    
    struct RecoverableDecode {
        
    }
    
}
```

もちろんこれだけでは終わりませんので、ここから少しずつ肉付けしていきます。まず全ての operator は `Publisher` ですので、`RecoverableDecode` も `Publisher` に適合する必要があります、そうすると `associatedtype` として `Output` と `Failure` が必要になります。

また、`RecoverableDecode` は必ず上流の `Publisher` から繋がる `operator` ですので、上流の型を表現する `Upstream` が必ず必要です；当然この `Upstream` は `Publisher` です。

他にこの operator は何かしらをデコードするので、`Decoder` も必要です；通常の `.decode` operator として受け取るデコーダーは `TopLevelDecoder` に適合したものですので、それに合わせて `Decoder: TopLevelDecoder` にすれば OK です。

そして最後に、各ジェネリクスの関係ですが、ここでデコードしかしていないのと、デコード時にエラーだけデフォルト値に置き換えているので、自分自身の `Failure` は `Upstream.Failure` そのままです；ただし自分の `Output` はデコードした型になりますので、`Upstream` とは関係ないですが、その代わり `Output` は必ず `Decodable` である必要だし、`Decoder.Input` も必ず `Upstream.Output` である必要があります。これで型の宣言がこうなります：

```swift
    struct RecoverableDecode<Upstream: Publisher, Decoder: TopLevelDecoder, Output: Decodable>: Publisher where Decoder.Input == Upstream.Output {
        typealias Failure = Upstream.Failure
    }
```

ジェネリクスを解決したら、次は必要なパラメータです。まず oeprator である以上、必ず上流をゴニョゴニョしますので、`upstream: Upstream` はもちろん必要です；次に上流が流し込んだデータをデコードするので、`decoder: Decoder` も必要です；最後にデコード失敗した時のデフォルト値を返さなければいけないので、`defaultValue: Output` も必要です。そしてこれらのプロパティーは外部に公開する必要が一切ないので、全て `private` で隠しておきましょう。

```swift
    struct RecoverableDecode<Upstream: Publisher, Decoder: TopLevelDecoder, Output: Decodable>: Publisher where Decoder.Input == Upstream.Output {
        // ...
        private let upstream: Upstream
        private let decoder: Decoder
        private let defaultValue: Output
        
        init(upstream: Upstream, decoder: Decoder, defaultValue: Output) {
            self.upstream = upstream
            self.decoder = decoder
            self.defaultValue = defaultValue
        }
    }
```

さて、いよいよ肝心な operator としての機能の実装になります。`Publisher` に適合する以上、`func receive<S: Subscriber>(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input` メソッドが必要になりますが、逆にいうと最後はこのメソッドだけさえ実装すれば万事解決です。ただその前に、このメソッドのジェネリクスを先に見ていきましょう。

ここで `S: Subscriber` のジェネリクスがありますが、これはつまり下流の購読者です。購読者は `Input` として自分自身の `Output` と同じ型、そして `Failure` として自分自身の `Failure` と同じ型である必要があるので、`where Self.Failure == S.Failure, Self.Output == S.Input` の型制限がかかります。

そしてその `S` である下流の購読者は、購読するときに走る処理がこの `receive` 処理ですので、だからこれを正しく実装すれば、繋がるとき購読処理がちゃんと動いて上流のデータが下流に流されます。ここの「正しい処理」というのは、上流の `Publisher` を `S: Subscriber` に `subscribe` する、というだけのことですので、総合すると、このメソッドはこんなふうに書けば OK です：

```swift
    struct RecoverableDecode<Upstream: Publisher, Decoder: TopLevelDecoder, Output: Decodable>: Publisher where Decoder.Input == Upstream.Output {
        // ...
        func receive<S: Subscriber>(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input {
            upstream
                .flatMap({ value in
                    Just(value)
                        .decode(type: Output.self, decoder: decoder)
                        .catch { _ in
                            Just(defaultValue)
                        }
                })
                .subscribe(subscriber)
        }
        
    }
```

`flatMap` の中身は上で紹介したコードと全く一緒で、最後に `.subscribe(subscriber)`[^subscribe] を繋ぐだけで、購読処理が実装されます。ね？意外と難しくないでしょ？

ただしもちろんこれだけではメソッドチェーンには使えないので、最後は `Publisher` に使えるメソッドチェーン用のメソッドを作れば終了です：

```swift
extension Publisher {
    
    func recoverableDecode<Output: Decodable, Decoder: TopLevelDecoder>(_ output: Output.Type, from decoder: Decoder, onErrorJustReturn defaultValue: Output) -> Publishers.RecoverableDecode<Self, Decoder, Output> where Decoder.Input == Self.Output {
        return Publishers.RecoverableDecode(upstream: self, decoder: decoder, defaultValue: defaultValue)
    }
    
}
```

こうすれば、今度このようなシチュエーションは面倒な `flatMap` を書かなくても、`recoverableDecode` ひとつで面倒なことが全て解決されます；たとえば Playground ならこんな簡単な確認ができます：

```swift
var cancellables: Set<AnyCancellable> = []

let datum: [Data] = ["1", nil, "3"]
    .map({ $0.map { try! JSONEncoder().encode($0) } ?? Data() })

datum.publisher
    .recoverableDecode(String.self, from: JSONDecoder(), onErrorJustReturn: "Decode Error")
    .sink(receiveValue: { print($0) })
    .store(in: &cancellables)
// ["1", "Decode Error", "3"]
```

operator の作り方を理解できれば、独自の処理を挟みたい時は簡単にそのための operator が作れるようになりますので、ぜひ色々作ってみましょう :muscle: 

[^reactive]: 厳密には Combine では Hot/Cold の概念がなかったり、Stream の意識が薄かったりで、一般的なよくあるリアクティブライブラリとは異なる部分も多いので、筆者はあまり Combine を「リアクティブ」と呼ぶのはあまり好きではありませんが、間違いなくリアクティブにインスパイアされたはずだし、共通するコンセプトもかなり多いので、イメージが掴みやすいため一応本記事でも「リアクティブ」と呼びます。

[^combine-publishers]: たとえば `Just<Int>` の publisher の後ろに `map` という operator を繋げると、その結果が `Map<Just<Int>, Output>` になるので、そのまま `Just<Int>` で公開すると型が一致しないためビルドエラーになるので、`AnyPublisher<Output, Never>` で公開し、operator を繋いだら最後に `eraseToAnyPublisher()` を呼び出して型を落とし込むことが実質強要されています。

[^rx-publisher]: 元の `BehaviorRelay` などの relay や subject を購読する側に暴露してしまうと、仕組み上購読する側も新しいイベントを流せてしまうので、本来であればそれらは `Observable` に落とし込んで公開するのが一番望ましいですが、まあ微妙にちょっと一手間かかるし、最低限のリアクティブの知識さえあればそんな汚いコード書かないはずですので、そのまま `BehaviorRelay` とかで公開してしまうコードもよく見掛けられます。

[^subscribe]: ただしこれ英語の文法としては実は微妙に違くて、`subscribe` は「購読する」の意味なので、この文法ですと「購読者**を**購読する」という逆の意味になってしまいます。もちろん `Publisher` から `Subscriber` への流れの方がメソッドチェーンが組みやすいのでこうなってしまうのは気持ちはわかりますが、せめて `getSubscribed(by: subscriber)` とかの API にして欲しかったりします。