Sma*_*alk 14 asynchronous swift combine
我在一个项目中成功地使用了 PromiseKit,直到 Xcode 11 beta 破坏了 PK v7。为了减少外部依赖,我决定废弃 PromiseKit。处理链式异步代码的最佳替代品似乎是使用新组合框架的 Futures。
我正在努力使用 Combine 复制简单的 PK 语法
前任。简单的 PromiseKit 链式异步调用语法
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}
Run Code Online (Sandbox Code Playgroud)
我明白:
async/await 的 Swift 标准库实现将解决这个问题(async/await 尚不存在,尽管Chris Latter 本人有很多喋喋不休和参与)
我可以使用信号量进行复制(容易出错?)
flatMap 可用于链接 Futures
我想要的异步代码应该能够按需调用,因为它涉及确保用户登录。我正在努力解决两个概念性问题。
如果我将 Futures 包装在一个方法中,sink以处理结果,则该方法似乎在订阅者被调用之前超出了范围sink。
由于 Futures 只执行一次,我担心如果我多次调用该方法,我只会从第一次调用中得到旧的、陈旧的结果。要解决这个问题,也许我会使用 PassthroughSubject?这允许按需调用发布者。
问题:
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
Run Code Online (Sandbox Code Playgroud)
mat*_*att 21
这不是你整个问题的真正答案——只是关于如何开始使用 Combine 的部分。我将演示如何使用Combine 框架链接两个异步操作:
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>
Run Code Online (Sandbox Code Playgroud)
首先,你关于持久化的问题的答案是:最终订阅者必须持久化,这样做的.store方法是使用方法。通常,您将拥有一个Set<AnyCancellable>作为属性,就像这里一样,您只需调用.store管道中的最后一件事即可将您的订阅者放入其中。
接下来,在这个管道中,我.handleEvents只是在管道移动时给自己一些打印输出。这些只是诊断,在实际实现中不存在。所有的print陈述纯粹是为了让我们可以谈论这里发生的事情。
那么会发生什么呢?
start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done
Run Code Online (Sandbox Code Playgroud)
所以你可以看到我们链接了两个异步操作,每个操作需要 3 秒。
我们是怎么做的?我们从一个 Future 开始,promise当它完成时,它必须用一个 Result 作为完成处理程序调用它的传入方法。之后,我们曾经.flatMap生成另一个Future 并将其投入运行,再次做同样的事情。
所以结果并不漂亮(就像 PromiseKit),但它是一个异步操作链。
在Combine 之前,我们可能已经使用某种Operation / 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>
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,我们的 Future 发布者的想法只需要传递promise回调;他们实际上不必是打电话给他们的人。一个promise回调由此可以随时随地调用,所以我们将不会继续在那之前。
因此,您可以很容易地看到如何delay用真正的异步操作替换人工操作,该操作以某种方式持有此promise回调并可以在它完成时调用它。此外,我的承诺 Result 类型纯粹是人为的,但您可以再次看到它们如何用于在管道中传达有意义的东西。当我说 时promise(.success(true)),这会导致true管道末端弹出;我们在这里忽略了这一点,但它可能是某种彻头彻尾的有用价值,甚至可能是下一个 Future。
(还要注意,我们可以.receive(on: DispatchQueue.main)在链中的任何一点插入,以确保紧随其后的内容在主线程上启动。)
我还想到我们可以通过将我们的 Future 发布者移到常量中来使语法更简洁,也许更接近 PromiseKit 可爱的简单链。但是,如果您这样做,您可能应该将它们包装在 Deferred 发布者中以防止过早评估。例如:
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>
}
Run Code Online (Sandbox Code Playgroud)
马特的答案是正确的,用于flatMap链接承诺。我在使用 PromiseKit 时养成了返回 promise 的习惯,并将其带到了 Combine(返回 Futures)中。
我发现它使代码更易于阅读。这是马特关于该建议的最后一个例子:
var storage = Set<AnyCancellable>()
func async1() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async1")
promise(.success(true))
}
}
}
func async2() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}
}
override func viewDidLoad() {
print("start")
async1()
.flatMap { _ in async2() }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
Run Code Online (Sandbox Code Playgroud)
请注意,它AnyPublisher也将用作返回值,因此您可以抽象出Future并让它返回AnyPublisher<Bool, Error>:
func async2() -> AnyPublisher<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}.eraseToAnyPubilsher()
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6109 次 |
| 最近记录: |