简化的 Future<Value, Error>
实现
Future 表示一项任务的结果,该结果可能现在可用、将来可用或永远不可用。 Future 提供了一种简化的 Future<Value, Error>
类型,其设计考虑了人体工程学和性能。
Futures 允许使用熟悉的函数(如 map
、flatMap
、zip
、reduce
等)组合任务,所有这些函数都易于学习和使用。
wait
map
· flatMap
· mapError
· flatMapError
· zip
· reduce
first
· forEach
· after
· retry
· materialize
要了解更多信息,请参阅完整的 API 参考。 准备好安装后,请按照 安装指南 进行操作。 有关支持平台的列表,请参阅 要求。
让我们从可用类型的概述开始。 中心类型当然是 Future
struct Future<Value, Error> {
public typealias Result = Swift.Result<Value, Error>
var result: Result? { get }
func on(success: ((Value) -> Void)?, failure: ((Error) -> Void)?, completion: (() -> Void)?)
}
Future
使用两个泛型参数进行参数化 –Value
和Error
。 这使我们能够利用 Swift 的类型安全功能,并使用Never
对永不失败的 futures 进行建模 –Future<Value, Never>
。
要创建 future,通常使用 Promise
func someAsyncTask() -> Future<Value, Error> {
let promise = Promise<Value, Error>()
performAsyncTask { value, error in
// If success
promise.succeed(value: value)
// If error
promise.fail(error: error)
}
return promise.future
}
Promise
是线程安全的。 您可以从任何线程和任意次数调用succeed
或fail
– 只有第一个结果会被发送到Future
。
如果工作的结果在创建 future 时已经可用,请使用以下初始化器之一
// Init with a value
Future(value: 1) // Inferred to be Future<Int, Never>
Future<Int, MyError>(value: 1)
// Init with an error
Future<Int, MyError>(error: .dataCorrupted)
// Init with a throwing closure
Future<Int, Error> {
guard let value = Int(string) else {
throw Error.dataCorrupted
}
return value
}
这些
init
方法不需要分配,这使它们非常快,比分配Promise
实例更快。
要将回调(每个回调都是可选的)附加到 Future
,请使用 on
方法
let future: Future<Value, Error>
future.on(success: { print("received value: \($0)") },
failure: { print("failed with error: \($0)") }),
completion: { print("completed") })
如果 future 已经有结果,则会立即执行回调。 如果 future 还没有结果,则在解析 future 时执行回调。 Future 保证它只能用一个结果解析,回调也保证只运行一次。
默认情况下,回调在 .main
调度器上运行。 如果任务在主线程上完成,则会立即执行回调。 否则,它们会被调度为在主线程上异步执行。
有关基本原理和更多信息,请参阅 线程处理。
使用 wait
方法阻止当前线程并等待 future 收到结果
let result = future.wait() // Mostly useful for testing and debugging
如果 future 已经有结果,您可以同步读取它
struct Future<Value, Error> {
var value: Value? { get }
var error: Error? { get }
var result: Result? { get }
}
使用熟悉的 map
和 flatMap
函数来转换 future 的值和链式 futures
let user: Future<User, Error>
func loadAvatar(url: URL) -> Future<UIImage, Error>
let avatar = user
.map { $0.avatarURL }
.flatMap(loadAvatar)
如果您不熟悉 flatMap
,起初可能很难理解它。 但一旦明白了,使用它就会成为第二天性。
实际上有几个
flatMap
变体。 额外的变体允许您无缝地混合可以产生错误的 futures 和不能产生错误的 futures。
Future
具有类型化的错误。 要从一种错误类型转换为另一种错误类型,请使用 mapError
let request: Future<Data, URLError>
request.mapError(MyError.init(urlError:))
使用 flatMapError
从错误中“恢复”。
如果您有一个从不产生错误的 future (
Future<_, Never>
),您可以使用castError
方法将其转换为可以产生 *任何* 错误的 future。 但在大多数情况下,不需要这样做。
使用 zip
将最多三个 futures 的结果合并到一个 future 中
let user: Future<User, Error>
let avatar: Future<UIImage, Error>
Future.zip(user, avatar).on(success: { user, avatar in
// use both values
})
或者等待多个 futures 的结果
Future.zip([future1, future2]).on(success: { values in
// use an array of values
})
使用 reduce
组合多个 future 的结果
let future1 = Future(value: 1)
let future2 = Future(value: 2)
Future.reduce(0, [future1, future2], +).on(success: { value in
print(value) // prints "3"
})
除了主要接口之外,还有一组 Future
的扩展,其中包括多个便利函数。 这里没有提到所有这些函数,请查看 FutureExtensions.swift
以查找更多信息!
使用 first
等待第一个 future 成功
let requests: [Future<Value, Error>]
Future.first(requests).on(success: { print("got response!") })
使用 forEach
依次执行工作
// `startWork` is a function that returns a future
Future.forEach([startWork, startOtherWork]) { future in
// In the callback you can subscribe to each future when work is started
future.on(success: { print("work is completed") })
}
使用 after
在给定的时间间隔后生成一个值。
Future.after(seconds: 2.5).on { print("2.5 seconds passed") })
使用 retry
执行给定的尝试次数以成功完成工作。
func startSomeWork() -> Future<Value, Error>
Future.retry(attempts: 3, delay: .seconds(3), startSomeWork)
Retry 非常灵活。 它允许您指定多种延迟策略,包括指数退避,在重试之前检查错误等等。
这一个非常吸引人。 它将 Future<Value, Error>
转换为 Future<Future<Value, Error>.Result, Never>
– 一个永不失败的 future。 它总是以初始 future 的结果成功。 现在,您为什么要这样做? 事实证明,materialize
与其他函数(如 zip
、reduce
、first
等)配合得非常好。 所有这些函数在其中一个给定的 future 失败时立即失败。 但是使用 materialize
,您可以更改这些函数的行为,以便它们等待直到所有 future 都被解析,无论成功还是失败。
请注意,我们使用原生
Never
类型来表示永远不会产生错误的情况。
Future.zip(futures.map { $0.materialize() }).on { results in
// All futures are resolved and we get the list of all of the results -
// either values or errors.
}
在 iOS 上,用户希望 UI 渲染同步发生。 为了适应这一点,默认情况下,回调使用 Scheduler.main
运行。 如果在主线程上,它会立即运行工作,否则在主线程上异步运行。 该设计类似于 RxSwift 等响应式框架。 它为使用传统上设计为异步的 futures 开辟了一个全新的领域。
有三种调度器可用
enum Scheduler {
/// If the task finishes on the main thread, the callbacks are executed
/// immediately. Otherwise, they are dispatched to be executed
/// asynchronously on the main thread.
static var main: ScheduleWork
/// Immediately executes the given closure.
static var immediate: ScheduleWork
/// Runs asynchronously on the given queue.
static func async(on queue: DispatchQueue, flags: DispatchWorkItemFlags = []) -> ScheduleWork
}
ScheduleWork
只是一个函数,因此您可以轻松提供自定义实现。
要更改调用回调的调度器,请使用 observe(on:)
// There are two variants, one with `DispatchQueue`, one with `Scheduler`.
// Here's the one with `DispatchQueue`:
future.observe(on: .global())
on(success: { print("value: \($0)" })
您还可以使用 observe(on:)
在后台队列上执行转换,例如 map
、tryMap
等
future.observe(on: .global())
.map { /* heavy operation */ }
请记住,只有由
observe(on:)
直接返回的 future 才能保证在给定的队列(或调度器)上运行其延续。
取消是一个与 Future
正交的问题。 将 Future
视为一个简单的回调替换 – 回调不支持取消。
Future
实现了用于异步任务的协作取消的 CancellationToken
模式。 通过取消令牌源创建令牌。
let cts = CancellationTokenSource()
asyncWork(token: cts.token).on(success: {
// To prevent closure from running when task is cancelled use `isCancelling`:
guard !cts.isCancelling else { return }
// Do something with the result
})
// At some point later, can be on the other thread:
cts.cancel()
要取消多个异步任务,您可以将相同的令牌传递给所有任务。
实现支持取消的异步任务很容易
func loadData(with url: URL, _ token: CancellationToken = .none) -> Future<Data, URLError> {
let promise = Promise<Data, URLError>()
let task = URLSession.shared.dataTask(with: url) { data, error in
// Handle response
}
token.register(task.cancel)
return promise.future
}
该任务完全控制取消。 您可以忽略它,您可以使用特定错误使 promise 失败,返回部分结果,或者根本不解析 promise。
CancellationTokenSource
本身是使用Future
构建的,并受益于其所有性能优化。
Async/await 通常构建在 futures 之上。 当 async/await 支持最终添加到 Swift 时,相对容易用 async/await 替换使用 futures 的代码。
有一个构建在 Future 之上的 (阻塞) 版本 的 async/await。 它不适合在生产中使用。
Future 中的每个功能都经过精心设计,考虑了性能。
我们避免动态调度,减少分配和释放的数量,避免做不必要的工作并尽可能少地锁定。 方法通常以一种不太优雅但性能更高的方式实现。
还有一些关键的设计差异使 Future 优于其他框架。 一个例子是 Future
类型本身,它被设计为 struct,这允许在没有单个分配的情况下执行一些常见操作。
Future | Swift | Xcode | 平台 |
---|---|---|---|
Future 1.4 | Swift 5.1 | Xcode 11 | iOS 11.0 / watchOS 4.0 / macOS 10.13 / tvOS 11.0 / Linux |
Future 1.1 | Swift 5.0 | Xcode 10.2 | iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0 |
Future 1.0 | Swift 4.2 | Xcode 10.1 | iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0 |
Future 0.17 | Swift 4.0 | Xcode 9.2 | iOS 9.0 / watchOS 2.0 / macOS 10.11 / tvOS 9.0 |
Future
在 MIT 许可下可用。 有关更多信息,请参阅 LICENSE 文件。