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つの概念的な問題に取り組んでいます。
結果を処理するためにsink
を使用してFuturesをメソッドにラップすると、サブスクライバーがsink
によって呼び出される前に、メソッドがスコープ外になるようです。
Futureは1回しか実行されないので、メソッドを複数回呼び出した場合、最初の呼び出しから古い古くなった結果しか得られないのではないかと心配しています。これを回避するには、PassthroughSubjectを使用しますか?これにより、パブリッシャーをオンデマンドで呼び出すことができます。
質問:
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
これはあなたの質問全体に対する本当の答えではありません-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>
}
_
このフレームワークをSwiftコルーチンに使用できます。また、Combineとともに使用することもできます- https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let future: Future<Bool, Error>
let coFuture = future.subscribeCoFuture()
let bool = try coFuture.await()
}