FutureX 1.1.1

FutureX 1.1.1

kean 维护。



FutureX 1.1.1


精简的 Future<Value, Error> 实现


突出的Future 代表任务的结果,可能已经可用,可能在未来,也可能永不。 FutureX 通过考虑到人机工程和性能,提供了一个精简的 Future<Value, Error> 类型。

未来使用类似 mapflatMapzipreduce 等熟悉的函数来组合任务,这些都是易于学习和使用的。

入门

快速入门指南

让我们先了解一下可用的类型。当然,中心是 Future 和它的 Result

struct Future<Value, Error> {
    var result: Result? { get }
    
    func on(success: ((Value) -> Void)?, failure: ((Error) -> Void)?, completion: (() -> Void)?)

    enum Result {
        case success(Value), failure(Error)
    }
}

Future 由两个泛型参数 - ValueError 参数化。这使我们能够利用 Swift 的类型安全功能,并使用 Never 模型永不失败的未来 - Future<Value, Never>

创建未来

通常,你会使用一个 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

如果工作结果在创建未来时已经可用,你可以使用以下一些初始化器:

// 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" })

如果未来已经有一个结果,则回调会立即执行。如果未来还没有结果,那么回调将在未来被解析时执行。未来保证只能解析一次结果,回调也保证只运行一次。

默认情况下,回调在 .main 调度程序上运行。如果任务在主线程上完成,回调会立即执行。否则,它们会被分派到在主线程上异步执行。

有关理由和更多信息,请参阅 线程

wait

使用 wait 方法可以使当前线程阻塞,等待未来接收结果。

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

result

如果未来已经有了结果,你可以同步读取它。

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

函数式组合

mapflatMap

使用熟悉的 mapflatMap 函数来转换未来值得并链式调用未来

let user: Future<User, Error>
func loadAvatar(url: URL) -> Future<UIImage, Error>

let avatar = user
    .map { $0.avatarURL }
    .flatMap(loadAvatar)

如果您不熟悉 flatMap,一开始可能难以理解。但一旦理解了,使用它就变得自然而然。

实际上不仅有单个的 flatMap,还有几个 flatMap 变体。额外的变体允许您无缝地混合可能产生错误和不会产生错误的未来。

mapErrorflatMapError

Future 有类型化错误。要从一个错误类型转换到另一个错误类型,请使用 mapError

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

使用 flatMapError 来“恢复”错误。

如果您有一个永远不会产生错误的未来 (Future<_, Never>),您可以将其强制转换为可以产生 任何 错误的未来,使用 castError 方法。但在大多数情况下,这并不是必需的。

zip

使用 zip 来结合最多三个未来的结果,在单个未来中

let user: Future<User, Error>
let avatar: Future<UIImage, Error>

Future.zip(user, avatar).on(success: { user, avatar in
    // use both values
})

也可以等待多个未来的结果

Future.zip([future1, future2]).on(success: { values in
    // use an array of values
})

reduce

使用 reduce 来结合多个未来的结果

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 来等待第一个未来任务成功

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)

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

materialize

这个功能非常有趣。它将 Future<Value, Error> 转换为 Future<Future<Value, Error>.Result, Never> –一个永远不会失败的未来。它总是以初始未来的结果成功完成。现在,你为什么想要这样做呢?事实上,materialize 与其他函数如 zipreducefirst 等组合得非常好。所有这些函数会在给定的任何一个未来失败时立即失败。但是与 materialize 一起,你可以更改这些函数的行为,使它们会等待直到所有未来都解决,无论是成功还是错误。

请注意,我们使用本地的 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等响应式框架。它为使用设计时即为异步的future开辟了全新的领域。

有三个调度器可用

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视为简单的回调替代品——回调不支持取消。

FutureX实现了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
}

任务对取消有完全的控制。您可以忽略它,可以使用特定的错误失败承诺,返回部分结果,或者根本不解决承诺。

CancellationTokenSource本身是用Future构建的,并受益于其所有性能优化。

Async/Await

Async/await通常建立在future之上。当Swift最终添加async/await支持时,它会相对容易地将使用futures的代码替换为async/await。

有一个基于FutureX的(阻塞)async/await的版本。它不需要在生产环境中使用。

性能

FutureX中的每个功能都是考虑到性能而设计的。

我们避免动态分发,减少分配和回收的数量,避免做不必要的操作并尽可能减少锁定。方法通常以不那么优雅但更高效的方式实现。

此外,还有一些关键的设计差异使得 FutureX 在其他框架中具有优势。一个例子就是 Future 类型本身,它被设计为结构体,允许在不进行单个分配的情况下执行一些常见操作。

需求

FutureX Swift Xcode 平台
FutureX 1.1 Swift 5.0 Xcode 10.2 iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0
FutureX 1.0 Swift 4.2 – 5.0 Xcode 10.1 – 10.2 iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0
FutureX 0.17 Swift 4.0 – 4.2 Xcode 9.2 – 10.1 iOS 9.0 / watchOS 2.0 / macOS 10.11 / tvOS 9.0

许可

FutureX 在 MIT 许可下提供。有关更多信息,请参阅 LICENSE 文件。