LoginSignup
3
1

More than 3 years have passed since last update.

Combine用途別逆引きメモ

Last updated at Posted at 2019-10-06

概要

こういう使いかたしたいけど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


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

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

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


// 以下の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

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

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などを使うと型がとてつもなく長くなり、実装の詳細が引数に滲み出てしまう。


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の型が長い呪文になってしまうので、関数の戻り値などのしてしまうと大変なことになる。

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()


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()

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


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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1