CombineによるPublisherの受け渡し


はじめに

RxSwiftを利用したVIPERアーキテクチャのソースコードをcombineで書き直している。
RxSwiftでは、InteractorがAPIから非同期処理で取得したデータをPresenterに渡す際、Singleオブジェクトとして扱っていた。SingleだったところをcombineではFutureオブジェクトにしたのだが、両者はGenericsの型の数が異なる。直接参考になるソースを見つけられなかったため我流のものだが書き残しておく。ただ、もっと良い方法があると思われるので、詳しい方にご教示いただきたいというのが正直なところである。

ソースコード

fetchFutureOnPresenter(flag: Bool)trueを入れればStringIntに変換された値が返ってくる。falseにすればPresenterを経由せずにエラーが投げられることをplaygroundで動作確認をした。


struct APIError: Error {
    var description: String
}

// InteractorがAPIにアクセスしてレスポンスを取得する想定
func fetchFutureOnInteractor(flag: Bool) -> AnyPublisher<String, Error> {

    return Future<String, Error>{ promise in

        ///
        /// 実際はここに非同期処理を書く想定
        ///

        // 非同期処理の結果によって、場合分け
        if flag {
            promise(.success("0"))
        } else {
            promise(.failure(APIError(description: "☓")))
        }
    }
    .eraseToAnyPublisher()
}

// PresenterがInteractorのメソッドから取得したデータをViewに渡す用に変換する想定
// flatMapでAnyPublisher<String, Error>からAnyPublisher<Int, Error>に変換
func fetchFutureOnPresenter(flag: Bool) -> AnyPublisher<Int, Error> {
    return fetchFutureOnInteractor(flag: flag)
        .flatMap { output -> AnyPublisher<Int, Error> in // クロージャの戻り値をIntにする
            Future<Int, Error>{ promise in
                promise(.success(Int(output)!))          // Intにキャスト、エラー(Failure)は、ここでは記載しない
            }
            .eraseToAnyPublisher()
    }
    .eraseToAnyPublisher()
}

// Presenterが購読(.sink)
let cancellable = fetchFutureOnPresenter(flag: /* true or false */)
.sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            break
        case .failure(let error):
            // Interactorの.failureはここに投げられる
            print("error \(error)")
        }
    }, receiveValue: { value in
        // このvalueを元にViewが参照する値を更新する想定
        print("value \(value)")
    })