AsyncSwift
使用 futures 和 async-await 语法在 Swift 中执行异步任务的工具包。
如何使用
- 您可以直接获取源代码。
- 使用 Cocoapods
并在您的 Swift 文件中pod "SomeAsyncSwift"
import SomeAsyncSwift
异步编程:futures, async, await
异步操作允许程序在等待其他操作完成时继续完成工作。以下是一些常见的异步操作:
- 通过网络获取数据。
- 向数据库写入。
- 从文件中读取数据。
AsyncSwift 允许您使用 AsyncAwaitFuture
类和 async
以及 await
关键字。
为了简单起见,AsyncAwaitFuture
的名称被简化为 AFuture
。
AFuture
AFuture
是一个带有一个泛型类型 result
参数的泛型类。AFuture
可以称为 SomeFuture
。
未来是具有特定结果类型的 AFuture
类的实例。未来代表异步操作的结果,并具有两种状态:resolved == true
和 resolved == false
。此外,由于未来不仅可能成功解决也可能出错,它还提供了 hasError
子状态。
如果未来已解决,则可以提供结果。result
属性是一个只读的泛型类型属性,它可以空值。未来可以以 nil
结果解决。这并不意味着有什么错误。因此,要确定未来是否已解决,您应检查 resolved
属性。在出现错误的情况下,resolved
属性显示 true
,但 hasError
属性也显示 true
。具体错误可以在 Error
属性中找到。它具有 Any?
类型,也可以是空值。
例子
let future1 = SomeFuture.delay(3)
这里 future1
是一个 AsyncAwaitFuture
,它在 3 秒后将解决。
let future2: AFuture<Int> = async {
for _ in 1...1000000 {
//do something
}
return 10 //resolve with 10
}
async
特殊构造函数返回一个未来。将在稍后详细介绍。
这里 future2
是一个 AsyncAwaitFuture
,在循环结束后它将用 10 解决。
let future3 = SomeFuture.wait([future1, future2])
这里 future3
是一个 AsyncAwaitFuture
,将在 future1
和 future2
解决后解决。
要等待 future1
和 future2
,您可以使用 await
关键字,这在这个实现中只是函数。
try! await(SomeFuture.wait([future1, future2]))
在这种情况下,你的当前线程将等待 future1
和 future2
。
与未来一起工作:异步和等待
async
和 await
关键字提供了一种声明式的方法来定义异步函数和获取其结果。
如果函数有声明返回类型,则将类型更新为 AFuture<T>
,其中 T 是函数返回的价值的类型。如果函数没有显式返回值,则返回类型为 AFuture<void>
或 SomeFuture
。函数应该在体内部使用 async
关键字。
func f0() -> AFuture<Int> {
async {
sleep(2) //to imitate some work in BG.
return 1
}
}
此函数可以具有参数
func f1(_ a: Int) -> AFuture<Int> {
async {
sleep(2)
return a
}
}
对于此类函数,您可以直接使用 await
以使您的代码等待解决未来。
let result = try! await(f1(5))
您可以使用函数来获取未来并观察其状态。为了观察未来状态,您可以使用几种方法
onSuccess
方法。在未来解决后将被调用。如果调用已解决的未来的 onSuccess
,它将被立即调用。
future.onResolved { result in
print(" Future resolved with:\(result)")
}
then
类似于 onSuccess
,但返回等待第一个未来解决的未来。这允许您创建未来链。然后有一个类型参数,它将被包含到其未来中。
let future = findEntryPoint().then(EntryPoint.self) { entryPoint in
guard let validEntryPoint = entryPoint else { fatalError()}
return self.runFromEnryPoint(validEntryPoint)
}.then(Void.self, finishChainWithOptinalParameter)
在这个示例中,所有函数都返回未来对象,因此都是异步函数。
always
类似于 onSuccess
,但它也适用于错误情况。
最后一个示例可以用 async await
构造函数重写
let entryPoint = await(findEntryPoint())
guard let validEntryPoint = entryPoint else { fatalError() /*to test exceptions*/}
let result = await(self.runFromEnryPoint(validEntryPoint))
如果您不希望线程在 await
中等待,请确保在 async
中调用它。async
将返回您可以订阅的未来。
注意:future 不会保留对自己引用的引用,这意味着在 futures 完成其工作之前,您始终需要保留对future的引用。
await
await
是一个使当前线程等待未来被解决的功能。如果 future 以错误方式解决,则抛出 AfutureErrors.FutureError。
在未返回未来功能的情况下使用时,您可以使用接受参数和函数的 await
变体。它接受 0 到 10 个参数和一个不返回 AFuture
的函数,并产生一个 AFuture<Type>
,其中 Type
是您的函数返回类型。
- 如果需要超过 10 个参数,则需要自己创建
await
。
错误处理
如何生成错误
如果异步函数抛出异常,future 可能会产生一个错误。
let future: AFuture<Int> = async {
for i in 1...1000000 {
if i == 10000 {
throw(TestErrors.testExcepton(howMany: i))
}
}
return 10
}
在这个示例中,future 不会返回 10,而是会发生类型为 TestErrors
的错误。以下是错误类型。
enum TestErrors: Error {
case testExcepton(howMany: Int)
}
如何在 future 中处理错误
await
如果在 future 错误的情况下抛出异常。因此,第一种方式是常规异常处理。
但您可以使用 try!
在 Future.wait
之前,因为它永远不会抛出。
请看这个例子
func testException() {
let future: AFuture<Int> = async {
for i in 1...1000000 {
if i == 10000 {
throw(TestErrors.testExcepton(howMany: i))
}
}
return 10
}
try! await(SomeFuture.wait([future]))
XCTAssert(future.resolved)
XCTAssert(future.hasError)
let error = future.error
if let validError = error as? TestErrors {
switch validError {
case .testExcepton(let howMany): XCTAssert(howMany == 10000)
}
} else {
XCTAssert(false)
}
}
所以如您所见,SomeFuture.wait([future])
的工作方式就好像 future 已经使用值解决。但将会有一个错误。
您还可以使用 onError
处理器。它的工作方式与 onSuccess
相同,但在出错的情况下。
如果您想要修复问题,请使用 catchError
future 连锁的示例
func testChain() {
var gotThenBlock = false
var wasInThen = false
var wasInAlways = false
let future: AFuture<String> = async { () -> Int in
for i in 1...1000000 {
if i == 10000 {
throw(TestErrors.testExcepton(howMany: i))
}
}
return 10
}.then(String.self) { result in
gotThenBlock = true
return async {
wasInThen = true
return "Ten"
}
}.catchError { _ in
return "10000"
}.always {
wasInAlways = true
}
let result = await(future)
XCTAssert(result == "10000")
XCTAssert(wasInAlways)
XCTAssert(!gotThenBlock)
XCTAssert(!wasInThen)
}
在这个测试中,第一个 future 提供 Int
结果,下一个 future 提供 String
,但第一个 future 提供了错误。我们通过 catchError
捕获它,并在此提供修复的值。注意:catchError
是一个新的 future。这意味着它也可以有 then
并且可以提供错误。因此,您可以连续有几个 catchError
。
Always
在任何情况下都会被调用。
await
中的错误
处理 第一种选择是使用标准的 try-catch
,因为如果 Future
出错,await
会抛出异常。
由于 await
接收 AFuture
,您可以生成给 future 的 catchError
。如果 catchError
总是产生值,您可以直接使用 try!
。这不是推荐的方式,因为 catchError
也可能返回错误。这里这样做是为了简便。
func testExceptionsInAwait() {
let future: AFuture<Int> = async {
for i in 1...1000000 {
if i == 10000 {
throw(TestErrors.testExcepton(howMany: i))
}
}
return 10
}
let result = try! await(future.catchError { error in
if let validError = error as? TestErrors {
switch validError {
case .testExcepton(let howMany): XCTAssert(howMany == 10000)
}
} else {
XCTAssert(false)
}
return 20
})
XCTAssert(result == 20)
}
如果您使用 await
到不提供 future 的函数,您就没有这样的机会,但可以使用第一种方式,即 try-catch
。
流和异步可观察属性
请查看 AsyncObservablesTests
和 StreamTests
以获取示例。