AsyncOpKit 将 Swift 泛型、错误处理和闭包引入 NSOperations 中,通过 AsyncOp
,一个仅支持 Swift 的泛型 NSOperation 子类,用于异步代码的组合。
AsyncOp
支持
您可以继承 AsyncOp,但由于它提供了泛型输入和输出的内置存储,并允许您通过闭包自定义行为,所以在许多情况下,您可以直接使用 AsyncOp 而不进行继承。
pod AsyncOpKit
在您的 podfile 中使用 use_frameworks!
,并在您使用 AsyncOpKit 的文件中用 import AsyncOpKit
AsyncOp.swift
和 AsyncOpTypes.swift
文件添加到您的项目中。AsyncOp
由我(Jed Lewison)编写,并拥有 MIT 许可证。这个文档以及这个软件仍然在开发中,欢迎提供反馈。
假设您想下载一张图片。您可以创建一个简单的 AsyncOp
,其输入类型为 NSURL,输出类型为 UIImage。让我们从以下开始
let imageDownloadOp = AsyncOp<NSURL, UIImage>()
由于我们已经知道了我们的 URL,我们可以直接提供它
imageDownloadOp.setInput(imageURL)
接下来,我们需要指定如何下载图片。我们在 onStart
闭包中这样做,如下所示
imageDownloadOp.onStart { asyncOp in
注意,在这个例子中,asyncOp
与 imageDownloadOp
相同。这里并不那么有用,但如果您从函数中返回操作,它可能会很有用。
首先我们必须获取输入,它存储在属性中,这是一个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
并将它们串联起来。
假设我们想要做到以下这一点(当然过程中会有错误处理)
由于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
之前提供要评估的函数,如果未满足前置条件可以防止操作执行。AsyncOperation提供用于与Objective-C的向后兼容。它不提供AsyncOp的所有功能,但它移除了异步操作中涉及的大量模板代码,并允许您指定结果值和错误值。AsyncOperation适用于Obj-C。对于Swift 1.2,您可以选择只复制AsyncOperation文件或使用pod AsyncOpKit, '0.0.8'
。