web-dev-qa-db-ja.com

Combine + Swiftを使用してPromiseKitスタイルのチェーン非同期フローを複製する方法

Xcode 11のベータ版がPK v7に違反するまで、プロジェクトでPromiseKitを正常に使用していました。外部の依存関係を減らすために、私はPromiseKitを破棄することにしました。チェーンされた非同期コードを処理するための最良の代替は、新しいCombineフレームワークを使用したFuturesのようです。

Combineを使用して単純なPK構文を複製するのに苦労しています

例単純なPromiseKitチェーン非同期呼び出し構文

getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}

わかります:

A Swift async/awaitの標準ライブラリ実装はこの問題を解決します(async/awaitはまだ存在していませんが、おしゃべりや関与が多いにもかかわらず Chris後者自身

セマフォを使用して複製できます(error-prone?

flatMapを使用してFutureをチェーンできます

ユーザーがログインしていることの確認に関係しているので、必要な非同期コードはオンデマンドで呼び出せるはずです。2つの概念的な問題に取り組んでいます。

  1. 結果を処理するためにsinkを使用してFuturesをメソッドにラップすると、サブスクライバーがsinkによって呼び出される前に、メソッドがスコープ外になるようです。

  2. Futureは1回しか実行されないので、メソッドを複数回呼び出した場合、最初の呼び出しから古い古くなった結果しか得られないのではないかと心配しています。これを回避するには、PassthroughSubjectを使用しますか?これにより、パブリッシャーをオンデマンドで呼び出すことができます。

質問:

  1. 呼び出しメソッドの外ですべてのパブリッシャーとサブスクライバーを保持する必要がありますか
  2. Swift標準ライブラリを使用して単純な連鎖非同期を複製し、これをSwiftインスタンスメソッドに埋め込むには、オンデマンドで呼び出して連鎖非同期を再起動します。上からの呼び出し?
//how is this done using Combine?
func startSync() {
 getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
4
Small Talk

これはあなたの質問全体に対する本当の答えではありません-Combineを使い始める方法に関する部分のみです。 Combineフレームワークを使用して2つの非同期操作をチェーンする方法を示します。

_    print("start")
    Future<Bool,Error> { promise in
        delay(3) {
            promise(.success(true))
        }
    }
    .handleEvents(receiveOutput: {_ in print("finished 1")})
    .flatMap {_ in
        Future<Bool,Error> { promise in
            delay(3) {
                promise(.success(true))
            }
        }
    }
    .handleEvents(receiveOutput: {_ in print("finished 2")})
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
_

まず、永続化に関する質問への答えは次のとおりです。最終的なサブスクライバーは永続化する必要があり、これを行う方法は_.store_メソッドを使用することです。通常、ここでは_Set<AnyCancellable>_をプロパティとして使用し、パイプラインの最後に_.store_を呼び出して、サブスクライバーをそこに配置します。

次に、このパイプラインでは、_.handleEvents_を使用して、パイプラインの移動に合わせて印刷を行います。これらは単なる診断であり、実際の実装には存在しません。すべてのprintステートメントは純粋なものなので、ここで何が起こっているかについて話すことができます。

それで、何が起こりますか?

_start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done
_

これで、2つの非同期操作が連鎖し、それぞれに3秒かかることがわかります。

どうやってやったの?私たちはFutureから始めました。Futureは、完了時にResultを完了ハンドラーとしてその着信promiseメソッドを呼び出す必要があります。その後、_.flatMap_を使用してanother Futureを生成し、操作を開始して、同じことを繰り返します。

したがって、結果は(PromiseKitのように)美しくはありませんが、非同期操作のチェーンです。

Combineの前に、おそらくこれを何らかの動作/ OperationQueue依存関係で実行していたでしょう。これは正常に機能しますが、PromiseKitの直接の読みやすさはさらに低下します。

少し現実的

以上のことをすべて踏まえた上で、少し現実的な書き換えを示します。

_var storage = Set<AnyCancellable>()
func async1(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async1")
        promise(.success(true))
    }
}
func async2(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async2")
        promise(.success(true))
    }
}
override func viewDidLoad() {
    print("start")
    Future<Bool,Error> { promise in
        self.async1(promise)
    }
    .flatMap {_ in
        Future<Bool,Error> { promise in
            self.async2(promise)
        }
    }
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
_

ご覧のとおり、Futureパブリッシャーであるアイデアは、promiseコールバックを渡すだけです。それらは実際にそれらを呼び出すものである必要はありません。したがって、promiseコールバックはどこからでも呼び出すことができ、それまでは先に進みません。

したがって、人工的なdelayを、このpromiseコールバックを何らかの形で保持し、完了時に呼び出すことができる実際の非同期操作で置き換える方法を簡単に確認できます。また、私の約束の結果タイプは純粋に人工的なものですが、パイプラインを介して意味のある何かを通信するためにそれらがどのように使用される可能性があるかについても確認できます。 promise(.success(true))と言うと、trueがパイプラインの最後をポップします。ここではそれを無視しますが、代わりに、ある種の実に有用な値になる可能性があります。

(チェーンの任意の場所に.receive(on: DispatchQueue.main)を挿入して、メインスレッドですぐに開始されるようにすることもできます。)

少しすっきり

また、Futureパブリッシャーを定数に移動することで、おそらくPromiseKitの素敵なシンプルチェーンに少し近づけることができます。ただし、そうする場合は、時期尚早な評価を防ぐために、それらを据え置きパブリッシャーでラップする必要があります。だから例えば:

_var storage = Set<AnyCancellable>()
func async1(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async1")
        promise(.success(true))
    }
}
func async2(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async2")
        promise(.success(true))
    }
}
override func viewDidLoad() {
    print("start")
    let f1 = Deferred{Future<Bool,Error> { promise in
        self.async1(promise)
    }}
    let f2 = Deferred{Future<Bool,Error> { promise in
        self.async2(promise)
    }}
    // this is now extremely neat-looking
    f1.flatMap {_ in f2 }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
_
2
matt

このフレームワークをSwiftコルーチンに使用できます。また、Combineとともに使用することもできます- https://github.com/belozierov/SwiftCoroutine

DispatchQueue.main.startCoroutine {
    let future: Future<Bool, Error>
    let coFuture = future.subscribeCoFuture()
    let bool = try coFuture.await()

}
0
Alex Belozierov