MiniFuture 0.5.0

MiniFuture 0.5.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2016 年 3 月
SPM支持 SPM

Tuomas Kareinen 维护。



MiniFuture

Swift 语言中使用的单子 Future 设计模式的实现,它使用 libdispatch 和 POSIX 锁和条件变量。设计灵感来自 Scala 的 scala.concurrent.Future

目前只有必要功能。我们正在生产中使用这个库,而且它似乎按预期工作。下面有一个基准测试用作压力测试,请参见 性能

要求

  • iOS >= 7.0(如果手动复制源文件安装)或 iOS >= 8.0(如有嵌入式框架安装)
  • Mac OS X >= 10.9
  • Xcode >= 7.0(Swift 2)

安装

手动

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以下。

未来工作

  • 实现Future取消和超时
  • 在Future上实现更多组合操作

许可

MiniFuture在MIT许可下发布。有关详细信息,请参阅LICENSE.txt