一个用于 Swift 5 及更高版本的无锁、异步 Result
。
Deferred<T, E: Error>
允许你将计算链接在一起。一个 Deferred
从一个不确定的、未解决的值开始。在稍后的某个时间点,它可能会变成已解决的。然后,只要该特定的 Deferred
实例存在,它的值就是不可变的。在 Deferred
变得已解决之前,依赖于它的计算可以使用无锁、线程安全的算法保存以供将来执行。这些计算的结果也由 Deferred
实例表示。在 Deferred
上下文中的任何点抛出的错误都会毫不费力地传播。
Deferred
最初是对 OCaml 的模块 Deferred 的近似。
let d = Deferred<Double, Never> { // holds a `Double`, cannot hold an `Error`
usleep(50000) // or a long calculation
return 1.0
}
print(d.value!) // 1.0, after 50 milliseconds
一个 Deferred
可以使用 notify
、map
和 flatMap
方法(以及其他方法)安排依赖于其结果的代码块以供将来执行。任意数量的此类代码块可以依赖于 Deferred
的结果。当抛出一个 Error
时,它会传播到所有依赖于它的 Deferred
。可以在转换链中插入一个 recover
步骤,以便处理潜在的错误。
let transform = Deferred<(Int) -> Double, Never> { i throws in Double(7*i) }
let operand = Deferred<Int, Error>(value: 6).delay(seconds: 0.1)
let result = operand.apply(transform: transform) // Deferred<Double, Error>
print(result.value!) // 42.0
Deferred
的 result
属性(以及它的附属属性,value
、error
和 get()
)将阻塞当前线程,直到它的 Deferred
变得已解决。Deferred
的其余部分是无锁的。
Deferred
可以在指定的 DispatchQueue
上,或在请求的 DispatchQoS
上运行其闭包。notify
、map
、flatMap
、apply
和 recover
方法也具有这些选项。默认情况下,闭包将安排在以当前服务质量 (qos) 类创建的队列上。
当存在请求时,任务执行。在创建 Deferred
对象时,会进行分配,但不会立即运行任何代码。当依赖于 Deferred
的代码请求结果时,任务会被安排执行。通过调用 notify()
、onValue()
、onError()
或 beginExecution()
方法(非阻塞);以及 result
、value
或 error
属性(阻塞)来发出请求。请求会沿着 Deferred
链向上传播。
可以通过使用 Deferred
的回调样式初始化器轻松实现支持取消和超时的长时间后台计算。它接受一个闭包,该闭包的参数是一个 Resolver
。Resolver
与 Deferred
的特定实例相关联,并允许你的代码成为该 Deferred
实例的数据源。它还允许数据源监视 Deferred
的状态。
func bigComputation() -> Deferred<Double, Never>
{
return Deferred {
resolver in
DispatchQueue.global(qos: .utility).async {
var progress = 0
repeat {
// first check that a result is still needed
guard resolver.needsResolution else { return }
// then work towards a partial computation
Thread.sleep(until: Date() + 0.001)
print(".", terminator: "")
progress += 1
} while progress < 20
// we have an answer!
resolver.resolve(value: .pi)
}
}
}
// let the child `Deferred` keep a reference to our big computation
let timeout = 0.1
let validated = bigComputation().validate(predicate: { $0 > 3.14159 && $0 < 3.14160 })
.timeout(seconds: timeout, reason: String(timeout))
do {
print(validated.state) // still waiting: no request yet
let pi = try validated.get() // make the request and wait for value
print(" ", pi)
}
catch Cancellation.timedOut(let message) {
print()
assert(message == String(timeout))
}
在上面的例子中,我们的计算闭包努力计算一个圆的周长与其直径的比率,然后通过将其与一个已知的近似值进行比较来执行一个粗略的输出验证。请注意,执行主计算的 Deferred
对象不会被此用户代码直接保留。当超时被触发时,计算被正确地放弃,并且该对象被释放。通常的取消可以以类似的方式执行。
Deferred
被小心地编写以通过利用 Swift 运行时的引用计数来支持取消。在每种情况下,当从包中的函数返回一个新的 Deferred
时,该返回的引用是唯一存在的强引用。这允许取消为整个计算链正常工作,即使它被应用于链的最后一个链接,而不管错误类型如何。
使用 swift 包管理器,将以下内容添加到你的包清单的依赖项中
.package(url: "https://github.com/glessard/deferred.git", from: "6.7.0")
此库是在 MIT 许可证下发布的。有关详细信息,请参见 LICENSE。