Open1

Combine 非同期処理中のloading stateを楽に管理できるextension

Rikuto KonRikuto Kon

ボタンを押されたらイベントが発火し、それに応じてflatMapでFutureを返す通信処理を実行するみたいなやつで二重送信を防ぎたい
かつフォームに送信中であるという状態を露出したい(Activity Indicator出したりボタンをdisabledにしたり)

毎回手続き型っぽい書き方するの嫌だな〜と思って書いたextensionがこちら

extension Publisher {
    func flatMap<T, P>(withLoading loading: CurrentValueSubject<Bool, Never>, maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> AnyPublisher<P.Output, P.Failure> where T == P.Output, P: Publisher, Self.Failure == P.Failure {
        filter { _ in !loading.value }
            .handleEvents(receiveOutput: { _ in loading.send(true) })
            .flatMap(transform)
            .handleEvents(receiveOutput: { _ in loading.send(false) })
            .eraseToAnyPublisher()
    }
}

使用例

let submitting = CurrentValueSubject<Bool, Never>(false)

// バリデーション結果とcombineLatestしてbuttonのdisabledにbindしたりできる
let canSubmit = submitting.map { !$0 } 

let result = onTapSubmit
    .flatMap(withLoading: submitting) {
        submit().receive(on: DispatchQueue.main)
    }.share()

イケてないところ

  • 返り値がAnyPublisherになる
    Publishers.FlatMapのまま返すことはできないのでFlatMapWithLoadingみたいな型を作る?
    それやるくらいならAnyPublisherでいい気はする

  • filter { _ in !loading.value }
    RxSwiftで言うところのwithLatestFromがCombineにないのでしゃーなし!独自実装してもいいんですけどね

もっと良い書き方あればマサカリお願いします