Swift 语言中使用的单子 Future 设计模式的实现,它使用 libdispatch 和 POSIX 锁和条件变量。设计灵感来自 Scala 的 scala.concurrent.Future。
目前只有必要功能。我们正在生产中使用这个库,而且它似乎按预期工作。下面有一个基准测试用作压力测试,请参见 性能。
将 Source
目录下的所有文件复制到您的项目中。您必须自己找出如何升级库的方法。
Future 是一项最终完成的计算承诺。我们使用 Try<T>
值类型来包装这些计算结果。它是一个有两个成员的枚举,分别是 Success<T>
和 Failure<ErrorType>
。前者表示 Future 调用方的成功计算,计算结果作为类型为 T
的关联值。后者表示计算失败,关联值为 ErrorType
。
例如,以下 Future 将最终完成 .Success(1)
let fut = Future<Int>.async {
NSThread.sleepForTimeInterval(0.2)
return .Success(1)
}
assert(succeeded.get() == .Success(1))
组合 Future 的基本操作是使用 Future#flatMap(_:)
。方法签名是
flatMap<U>(f: T throws -> Future<U>) -> Future<U>
它接受一个闭包,f
。如果当前的 Future 成功完成,则 f
将以 T
作为参数被调用。生成的返回值是下一次 Future 计算的结果。稍后,如果那个 Future 成功完成,将会调用传递给那个 Future 的闭包。这是 Future 的异步调用链。
如果当前的 Future 失败完成,将不会调用 f
。这将短路 Future 的调用链。
当调用 f
时,闭包可能会抛出一个错误。如果发生这种情况,返回值将是包含错误的新失败的 Future。
例如
enum Error: ErrorType {
case Deliberate(String)
}
let fut: Future<[Int]> = Future.async { .Success(0) }
.flatMap { Future.succeeded([$0, 1]) }
.flatMap { throw Error.Deliberate(String($0)) }
.flatMap { Future.succeeded($0 + [2]) }
assert(String(fut.get()) == "Failure(Deliberate(\"[0, 1]\"))")
Try<T>
和自动处理抛出的错误是从 Scala 2.10 吸取的。
所有异步操作都在libdispatch的默认全局并发队列中运行。传递给Future#flatMap(_:)
、Future#map(_:)
、Future#onComplete(_:)
和Future.async(_:)
的闭包总是在队列工作线程中执行。当通过闭包捕获的引用访问共享状态时,请使用适当的同步机制。
要运行Future任务,请使用Future.succeeded(_:)
和Future.failed(_:)
来包裹立即值。这些返回ImmediateFuture
对象,这是一个已完成成功或失败值的Future实现类。
对于在队列工作线程中稍后计算的异步任务,请使用Future.async(_:)
。您将一个块传递给async(_:)
,并从其中返回一个Success<T>
或Failure<ErrorType>
值。这里的Future实现类是AsyncFuture
。
为了将现有的异步接口与Future相适配,请使用Future.promise(_:)
。这返回一个PromiseFuture
对象,这是一个类似于Promise的Future实现类。将Future传递给现有的异步接口,并在接口的完成处理程序中,通过Future#resolve(_:)
或Future#reject(_:)
完成Future的成功或失败。您可以立即返回一个PromiseFuture
,以便期望Future的代码,并让PromiseFuture
对象稍后完成。
您还可以通过另一个Future的结果来完成PromiseFuture
。调用PromiseFuture#completeWith(_:)
并传递另一个Future作为参数。一旦Future完成,promise就会以与Future相同的结果完成。
获取Future句柄后,请使用Future#flatMap(_:)
或Future#map(_:)
来组合另一个依赖于先前Future完成结果的Future。使用Future#get()
等待Future的结果。使用Future#onComplete(_:)
添加一个回调,以便在Future完成时运行副作用。
extension String {
var trimmed: String {
return stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
}
func excerpt(maxLength: Int) -> String {
precondition(maxLength >= 0, "maxLength must be positive")
if maxLength == 0 {
return ""
}
let length = characters.count
if length <= maxLength {
return self
}
return self[startIndex..<startIndex.advancedBy(maxLength-1)].trimmed + "…"
}
}
/**
* Request a web resource asynchronously, immediately returning a handle to
* the job as a promise kind of Future. When NSURLSession calls the completion
* handler, we fullfill the promise. If the completion handler gets called
* with the contents of the web resource, we resolve the promise with the
* contents (the success case). Otherwise, we reject the promise with failure.
*/
func loadURL(url: NSURL) -> Future<NSData> {
let promise = Future<NSData>.promise()
let task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { data, response, error in
if let err: NSError = error {
promise.reject(err)
} else if let d = data {
promise.resolve(d)
} else {
promise.reject(Error.FailedLoadingURL(url))
}
})
task.resume()
return promise
}
/**
* Parse data as HTML document, finding specific contents from it with an
* XPath query. We return a completed Future as the handle to the result. If
* we can parse the data as an HTML document and the query succeeds, we return
* a successful Future with the query result. Otherwise, we return failed
* Future describing the error.
*
* Because this function gets called inside `Future#flatMap`, it's run in
* background in a queue worker thread.
*/
func readXPathFromHTML(xpath: String, data: NSData) throws -> Future<HTMLNode> {
let doc = try HTMLDocument.readDataAsUTF8(data)
let node = try doc.rootHTMLNode()
let found = try node.nodeForXPath(xpath)
return Future.succeeded(found)
}
let wikipediaURL = NSURL(string: "https://en.wikipedia.org/wiki/Main_Page")!
let featuredArticleXPath = "//*[@id='mp-tfa']"
let result = loadURL(wikipediaURL)
/* Future composition (chaining): when this Future completes successfully,
* pass its result to a function that does more work, returning another
* Future. If this Future completes with failure, the chain short-circuits
* and further flatMap methods are not called. Calls to flatMap are always
* executed in a queue worker thread.
*/
.flatMap { try readXPathFromHTML(featuredArticleXPath, data: $0) }
/* Wait for Future chain to complete. This acts as a synchronization point.
*/
.get()
switch result {
case .Success(let value):
let excerpt = value.textContents!.excerpt(78)
print("Excerpt from today's featured article at Wikipedia: \(excerpt)")
case .Failure(let error):
print("Error getting today's featured article from Wikipedia: \(error)")
}
更多内容请参阅Example/main.swift
。您可以运行这些示例
$ make example
# xcodebuild output…
./build/Example
Excerpt from today's featured article at Wikipedia: Upper and Lower Table Rock are two prominent volcanic plateaus just north of…
在Benchmark/main.swift
中有一个基准测试。它在一个循环中构建复杂的嵌套Future(代码中的futEnd
变量)2000次(NumberOfFutureCompositions
),并将它们链成一个大的组合Future(代码中的fut
变量)。然后基准测试等待Future完成。
我们重复进行500次(NumberOfIterations
),以获取完成每个组合Future所需时间的算术平均值和标准差。
使用发布构建配置编译它,它会启用-O
编译器标志。然后从终端运行它。
示例运行在MacBook Pro 2.6 GHz Intel Core i7 Haswell、16 GB 1600 MHz DDR3上
$ make benchmark
# xcodebuild output…
./build/Benchmark
iterations: 500, futures composed: 2000
warm up: 65 ms (± 4 ms)
measure: 65 ms (± 3 ms)
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
Target: x86_64-apple-macosx10.9
进程的总内存消耗保持在15 MB以下。
MiniFuture在MIT许可下发布。有关详细信息,请参阅LICENSE.txt
。