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