简化的 Future<Value, Error> 实现


Future 表示一项任务的结果,该结果可能现在可用、将来可用或永远不可用。 Future 提供了一种简化的 Future<Value, Error> 类型,其设计考虑了人体工程学和性能。

Futures 允许使用熟悉的函数(如 mapflatMapzipreduce 等)组合任务,所有这些函数都易于学习和使用。

入门

要了解更多信息,请参阅完整的 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 使用两个泛型参数进行参数化 – ValueError。 这使我们能够利用 Swift 的类型安全功能,并使用 Never 对永不失败的 futures 进行建模 – Future<Value, Never>

创建 Future

要创建 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 是线程安全的。 您可以从任何线程和任意次数调用 succeedfail – 只有第一个结果会被发送到 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

使用 wait 方法阻止当前线程并等待 future 收到结果

let result = future.wait() // Mostly useful for testing and debugging

result

如果 future 已经有结果,您可以同步读取它

struct Future<Value, Error> {
    var value: Value? { get }
    var error: Error? { get }
    var result: Result? { get }
}

组合

map, flatMap

使用熟悉的 mapflatMap 函数来转换 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。

mapError, flatMapError

Future 具有类型化的错误。 要从一种错误类型转换为另一种错误类型,请使用 mapError

let request: Future<Data, URLError>
request.mapError(MyError.init(urlError:))

使用 flatMapError 从错误中“恢复”。

如果您有一个从不产生错误的 future (Future<_, Never>),您可以使用 castError 方法将其转换为可以产生 *任何* 错误的 future。 但在大多数情况下,不需要这样做。

zip

使用 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

使用 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

使用 first 等待第一个 future 成功

let requests: [Future<Value, Error>]
Future.first(requests).on(success: { print("got response!") })

forEach

使用 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

使用 after 在给定的时间间隔后生成一个值。

Future.after(seconds: 2.5).on { print("2.5 seconds passed") })

retry

使用 retry 执行给定的尝试次数以成功完成工作。

func startSomeWork() -> Future<Value, Error>

Future.retry(attempts: 3, delay: .seconds(3), startSomeWork)

Retry 非常灵活。 它允许您指定多种延迟策略,包括指数退避,在重试之前检查错误等等。

materialize

这一个非常吸引人。 它将 Future<Value, Error> 转换为 Future<Future<Value, Error>.Result, Never> – 一个永不失败的 future。 它总是以初始 future 的结果成功。 现在,您为什么要这样做? 事实证明,materialize 与其他函数(如 zipreducefirst 等)配合得非常好。 所有这些函数在其中一个给定的 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:) 在后台队列上执行转换,例如 maptryMap

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

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 文件。