AsyncOpKit 1.2.3

AsyncOpKit 1.2.3

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布上次发布2016年6月
SPM支持 SPM

Jed Lewison 维护。



  • Jed Lewison

AsyncOpKit

AsyncOpKit 将 Swift 泛型、错误处理和闭包引入 NSOperations 中,通过 AsyncOp,一个仅支持 Swift 的泛型 NSOperation 子类,用于异步代码的组合。

AsyncOp 支持

  • 泛型输入和输出
  • 用于启动和取消工作、处理结果的闭包
  • 用于评估前置条件的闭包
  • 使 AsyncOp 依赖另一个的输入

您可以继承 AsyncOp,但由于它提供了泛型输入和输出的内置存储,并允许您通过闭包自定义行为,所以在许多情况下,您可以直接使用 AsyncOp 而不进行继承。

要求与安装

  • AsyncOp 已针对 iOS 8.0 及以上版本进行测试。理论上,也应该适用于 OS X、tvOS 和 WatchOS,但我没有进行测试。
  • 通过 CocoaPods:pod AsyncOpKit 在您的 podfile 中使用 use_frameworks!,并在您使用 AsyncOpKit 的文件中用 import AsyncOpKit
  • 或者只需将 AsyncOp.swiftAsyncOpTypes.swift 文件添加到您的项目中。

许可证/作者

AsyncOp 由我(Jed Lewison)编写,并拥有 MIT 许可证。这个文档以及这个软件仍然在开发中,欢迎提供反馈。

示例用法

假设您想下载一张图片。您可以创建一个简单的 AsyncOp,其输入类型为 NSURL,输出类型为 UIImage。让我们从以下开始

let imageDownloadOp = AsyncOp<NSURL, UIImage>()

由于我们已经知道了我们的 URL,我们可以直接提供它

imageDownloadOp.setInput(imageURL)

接下来,我们需要指定如何下载图片。我们在 onStart 闭包中这样做,如下所示

imageDownloadOp.onStart { asyncOp in

注意,在这个例子中,asyncOpimageDownloadOp 相同。这里并不那么有用,但如果您从函数中返回操作,它可能会很有用。

首先我们必须获取输入,它存储在属性中,这是一个AsyncOpValue,它存储输入值或者如果提供输入存在问题时,关联的错误。onStart是一个抛出异常的闭包,所以为了获取我们的值,我们可以在AsyncOpValue上调用一个抛出异常的函数,如果值存在就会成功,如果不存在就会抛出异常。如果它抛出异常,操作将以错误结束(稍后详细介绍)。

现在事情应该如下所示

imageDownloadOp.onStart { asyncOp in
    let imageURL = try asyncOp.input.getValue()

接下来我们需要发起一个网络请求,以获取存储在imageURL的数据。为了简化示例,让我们使用普通的NSURLSession来完成这个任务。

imageDownloadOp.onStart { asyncOp in
    let imageURL = try asyncOp.input.getValue()
    let dataTask = NSURLSession.sharedSession().dataTaskWithURL(imageURL) { data, response, error in
        // response handling here
    }
    dataTask.resume()
}

请注意,在这里我作弊了,我没有处理响应。这不仅对显而易见的原因很重要,而且如果我们没有告诉操作何时完成,一旦它开始,它将永远不会完成。

使用finish(with:)函数完成AsyncOp

一旦AsyncOp开始执行,它必须手动完成

您可以通过抛出异常来完成错误。在我们的示例中,请注意,如果try asyncOp.input.getValue()失败,因为抛出异常,所以操作将以错误结束。请注意,您不能在不重新抛出异常的闭包内部抛出异常。

除了抛出异常,您如何完成AsyncOp?以下是一个在前面示例的基础上进行的简单实现。

imageDownloadOp.onStart { asyncOp in
    let imageURL = try asyncOp.input.getValue()
    let dataTask = NSURLSession.sharedSession().dataTaskWithURL(imageURL) { data, _, error in
        if let data = data, image = UIImage(data: data) {
            asyncOp.finish(with: image)
        } else {
            asyncOp.finish(with: error ?? AsyncOpError.Unspecified)
        }
    }
    dataTask.resume()
}

从那个例子中可以吸取的关键是,为了完成一个操作,您需要调用它的finish函数。finish有多个方便的重载,您可以提供错误、输出值或标记取消操作。如果您的操作不产生任何输出,您可以使用AsyncVoid类型,并使用finishWithSuccess()而不是像finish(with: Void())这样的操作。

使用whenFinished获取AsyncOp的结果

一旦我们的操作完成,我们如何从这个操作中获取图片?我们使用whenFinished闭包。如果我们不关心错误,实现可能看起来像这样

imageDownloadOp.whenFinished { asyncOp in
    guard let image = try? asyncOp.output.getValue() else { return }
    imageView.image = image
}

由于AsyncOp使用泛型,并且因为我们指定了输出类型为UIImage,所以如果存在,image保证是UIImage。如果我们想处理错误,我们可以像这样对输出进行条件处理

imageDownloadOp.whenFinished { asyncOp in
    switch asyncOp.output {
    case .None(let asyncOpValueError):
        errorHandler.handleError(asyncOpValueError)
    case .Some(let image):
        imageView.image = image
    }
}

图片保证是一个图片,但现在我们可以检查错误。请注意,我们仅在主线程的whenFinished闭包中执行UI工作。因为默认情况下,whenFinished闭包在主队列中触发。要指定不同的队列,只需不接受默认参数,例如

imageDownloadOp.whenFinished(whenFinishedQueue: notMainThreadQueue) { asyncOp in

还应注意的是,您可以在任何时候提供whenFinished闭包,甚至在操作完成后,但只能这样做一次。

使用cancel()取消AsyncOp

一旦AsyncOp开始执行,处理取消操作的责任就由您承担。您可以使用onCancel闭包指定在被调用后要执行的操作,例如取消dataTask操作,但您仍然必须在实际执行期间适当的时刻检查操作的cancelled属性,以处理取消并完成操作。如果您选择尊重取消命令,应该使用finish(with: .Cancelled),通常在检查到取消后,在onStart的实现中执行。

通过AsyncOpInputProvider使用输入依赖项链式调用AsyncOp

假设我们想要对数据进行一些比仅仅尝试将其转换为UIImage更复杂的操作,也许我们想要调整图像大小并对其进行遮罩。我们可以在操作的onStart闭包中添加代码来完成这个任务,但这会使代码很快变得很难读。相反,我们想要创建两个或更多个AsyncOp并将它们串联起来。

假设我们想要做到以下这一点(当然过程中会有错误处理)

  1. 从网络上获取一些图像数据并提供一个原始图像
  2. 处理原始图像并提供最终的输出图像

由于CoreImage使向图像应用所有类型的过滤器变得更容易,现在我们想要我们的图像下载操作提供CIImage

let imageDownloadOp = AsyncOp<NSURL, CIImage>()
imageDownloadOp.setInput(imageURL)

并且我们想要创建一个新的操作,它接受CIImage并返回UIImage

let imageFilteringOp = AsyncOp<CIImage, UIImage>()

但是,这里有问题,对吧?如何在不写很多模板代码的情况下将imageDownloadOp的输出传递到imageFilteringOp的输入?幸运的是,AsyncOp使其变得简单

imageFilteringOp.setInputProvider(imageDownloadOp)

setInputProvider为目标操作提供了一个符合AsyncOpInputProvider的对象,使其能够在开始执行时请求其输入。此外,如果输入提供者是NSOperation,目标将提供者作为依赖项。这意味着,我们现在已经将我们的imageDownloadOp设置为imageFilteringOp的输入提供者,我们现在只需要在我们onStart闭包的最初获取我们的输入。例如

imageFilteringOp.onStart { asyncOp in
    let image = try asyncOp.input.getValue()

记住,getValue()抛出异常,而onStart是一个抛出异常的闭包,所以如果下载操作出错并且没有图像,操作将在这一点立即终止。否则,我们可以继续进行图像过滤,并在完成后确保使用finish(with: outputImage)

AsyncOp符合AsyncOpInputProvider,所以任何AsyncOp都可以提供输入到另一个AsyncOp,只要其输出类型与目标的输入类型匹配。多亏了NSOperation提供的依赖关系,输入提供者将只在它完成时被要求提供输入。

请记住,输入是AsyncOpValue枚举。虽然它是通过泛型使用强类型化的,但使用枚举包装器允许传播错误消息,因此您必须使用如上语法解包输入。

其他功能

本文档仍在进行中,AsyncOp本身也是如此。除了阅读代码外,您可能还想浏览测试以了解这里未涉及的其他功能,包括

  • 使用pause()resume()AsyncOp开始执行之前暂停其读入状态
  • 使用AsyncOpPreconditionEvaluator函数来评估前置条件。这些允许在调用onStart之前提供要评估的函数,如果未满足前置条件可以防止操作执行。

针对Objective-C和Swift 1.2版本的异步操作

AsyncOperation提供用于与Objective-C的向后兼容。它不提供AsyncOp的所有功能,但它移除了异步操作中涉及的大量模板代码,并允许您指定结果值和错误值。AsyncOperation适用于Obj-C。对于Swift 1.2,您可以选择只复制AsyncOperation文件或使用pod AsyncOpKit, '0.0.8'