Combine用途別逆引きメモ

# 概要

こういう使いかたしたいけどCombineだとどう書くの？という場合に陥った内容を思い返すためのメモです。
追加で何かあれば書き足して行こうと思います。

# 用途一覧

## Errorを返さないPublisherを作りたい

UIパーツなど、エラーパターンの実装が不要なSubjectを作りたい。
failureをながさないならNeverを使ってねとのこと

> Use Never if this Publisher does not publish errors.
https://developer.apple.com/documentation/combine/publisher/3204680-failure

```swift

// 型の指定でPublisher.FailureをNeverに指定
let subject = PassthroghSubject<Data, Never>
```

## Errorの場合はnilに変換したい

例えばHTTPリクエストの結果がエラーだった場合はnilで返したい場合

```swift

// 以下のPassthroughSubjectを
func httpRequest() -> PassthroughSubject<Data, Error>
// Dataをオプショナルにしてエラーのときはnilを返す。エラーは返さないようにしたい。
func httpRequestWrapper() -> PassthroughSubject<Data?, Never>
```

`map`をつかってnilを扱えるように変換、`catch`を使ってエラーをnilに置き換えるように変換。

Catchのときに値を置き換えたいならJustを使うと便利とのこと
>  A Just publisher is also useful when replacing a value with Publishers.Catch.
https://developer.apple.com/documentation/combine/just

```swift
struct HTTPRequester {
  /// HTTPリクエストの結果を受け取るsubjectと仮定
  func request() -> PassthroughSubject<Data, Error> { ... }
}

let requester: HTTPRequester

requester.request().map { (data) -> Data? in
  // <Data, Error>を<Data?, Error>に
  return data
}.catch { (_) -> Just<Data?> in
  // Errorの場合はnilに
  return Just(nil)
}.sink(receiveValue: { data in
  // 成功の場合はデータがそのまま、failureの場合はnilに変換されて流れてくる
  guard let responseData = data else { return }
})
```

## イベントの待ち合わせをしたい

Zipを使うと両方のイベントが発行された段階で値を受け取ることができる
https://developer.apple.com/documentation/combine/publisher/3204779-zip

```swift
struct HTTPRequester {
  /// HTTPリクエストの結果を受け取るsubjectと仮定
  func request() -> PassthroughSubject<Data, Error> { ... }
}
let requesterA = HTTPRequester()
let requesterB = HTTPRequester()

// zipでイベントの完了を待ち合わせる
// receiveValueのブロックの引数にtupleの値が入ってくる
let zipSubject = requesterA.request().zip(requesterB.request()).sink(receiveCompletion: { _ in }, receiveValue: { _ in })
```

片方が失敗するとreceiveValueが呼ばれないので注意が必要

## 型の名前が長いので短くしたい

zipやmapなどを使うと型がとてつもなく長くなり、実装の詳細が引数に滲み出てしまう。

```swift

let firstSubject = hogeRepository
        .reload()
        .map({ (hoge) -> Hoge? in
            return hoge
        })
        .catch({ (_) -> Just<Hoge?> in
            return Just(nil)
        })

let lastSubject = fugaRepository
        .reload()
        .map({ (fuga) -> Fuga? in
            return fuga
        })
        .catch({ (_) -> Just<Fuga?> in
            return Just(nil)
        })

let zipped = firstSubject.zip(lastSubject)
```

このときの`zipped`の型が長い呪文になってしまうので、関数の戻り値などのしてしまうと大変なことになる。

```swift
let zipped: Publishers.Zip<Publishers.Catch<Publishers.Map<PassthroughSubject<Hoge, Error>, Hoge?>, Just<Hoge?>>, Publishers.Catch<Publishers.Map<PassthroughSubject<Fuga, Error>, Fuga?>, Just<Fuga?>>>
```

この際に`func eraseToAnyPublisher()`を使うと型をAnyPublisherに変換できる。
https://developer.apple.com/documentation/combine/publisher/erasetoanypublisher()

```swift

let firstSubject = hogeRepository
        .reload()
        .map({ (hoge) -> Hoge? in
            return hoge
        })
        .catch({ (_) -> Just<Hoge?> in
            return Just(nil)
        })
        .eraseToAnyPublisher()

let lastSubject = fugaRepository
        .reload()
        .map({ (fuga) -> Fuga? in
            return fuga
        })
        .catch({ (_) -> Just<Fuga?> in
            return Just(nil)
        })
        .eraseToAnyPublisher()

let zipped = firstSubject.zip(lastSubject)
    .eraseToAnyPublisher()
```

これを使うと型がスッキリする

```swift

let zipped: AnyPublisher<(Hoge?, Fuga?), Just<Hoge?>.Failure>
```