这篇文章是对 WWDC 2015 Session 226: Advanced NSOperations 的一个小结,在那个视频中, Dave DeLong 分享了 NSOperation 的高级玩法,WWDC App 就是基于这套玩法做的,还是挺开阔思路的。
我们知道 NSOperation 可以执行一些后台操作,如 HTTP 请求,在 iOS 4.0 之前是基于 NSThread 来实现的,iOS 4.0 带了 GCD,NSOperation 底层也基于 GCD 重写了底层实现。
所以 NSOperation 是 GCD 的高层封装,同时也带来了一些更加便利的功能,比如取消任务,设置依赖等。在进入高级玩法前,先简单的介绍下 NSOperation 和 NSOperationQueue。
这个属性表示的是 NSOperationQueue 最多可以同时处理几个任务,假如我们希望它一次只处理一个,也就是线性 Queue,可以设置 maxConcurrentOperationCount = 1
中间的点表示任务的状态,在上一个任务完成前,下一个任务不会被执行,因为只有一个 worker。
如果希望一次能处理多个,将这个值设置为大于 1 即可,或者直接使用默认值,系统会自动设置一个合理的最大值。
从上面的图可以看到,正在被执行的任务的状态跟在后面排队的状态是不一样的,有这么几种状态:pending, ready, executing, finished, cancelled。
之前提到过 NSOperation 一个很重要的特性是可以被取消,但不同状态的取消处理也不一样。比如当 Operation 处于 pending, ready 状态时,系统可以去看一下这个 Operation 是否已经被取消了(判断 self.cancelled),如果是的话,就不执行任务了。但是当 Operation 处于 executing 状态时,取消的操作就只能自己处理了,比如
@implementation MyOperation: NSOperation - (void)main { // ... while (!self.cancelled) { // executing } } @end
NSOperation 还有一个很重要的特性是可以设置依赖
任务 A 需要等待 任务 B 和 任务 C 完成,才能被执行,而任务 B 需要等到 任务 D 完成才能被执行。
当然前提是这些 Operation 都需要被放到某个 Queue 里,这样它们的状态才会发生改变。
开发 App 的过程中,有一些逻辑是可以共用的,比如登录、网络状况等,最好可以组装起来,就像超能陆战队里的 megabot 一样
基于前面提到的 NSOperation / NSOperationQueue 的一些特点,苹果的工程师们想到了他们的解决方法。
Condition,也就是条件,它可以被附加到 Operation 上,只有当 Condition 被满足时,Operation 才能被执行。比如只有在有网络的情况下才能进行交易,这时「网络状况」就是附加给「交易」的 Condition。
一个 Condition 主要包含了 3 个方法:
// 1 static var isMutuallyExclusive: Bool { get } // 2 func dependencyForOperation(operation: Operation) -> NSOperation? // 3 func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void)
name
来实现的。 所以总结起来 Condition 主要干了这么三件事
来看一个简单的 Condition (来自 WWDC Sample)
struct ReachabilityCondition: OperationCondition { static let hostKey = "Host" static let name = "Reachability" static let isMutuallyExclusive = false let host: NSURL // 1 init(host: NSURL) { self.host = host } // 2 func dependencyForOperation(operation: Operation) -> NSOperation? { return nil } func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { ReachabilityController.requestReachability(host) { reachable in if reachable { // 3 completion(.Satisfied) } else { let error = NSError(code: .ConditionFailed, userInfo: [ OperationConditionKey: self.dynamicType.name, self.dynamicType.hostKey: self.host ]) // 4 completion(.Failed(error)) } } } }
dependencyForOperation
,因为生成依赖 Operation 的目的是当这个 Operation 运行完后,可以在 evaluateForOperation 时获取之前的运行结果,而这里直接调用 ReachabilityController 的 requestReachability 方法就可以了,所以就免去了这一步。 completion(.Satisfied)
completion(.Failed(error))
Operation
继承自 NSOperation
,同时添加了一些方法,主要可以分为 4 部分
evaluateForOperation
方法 设置状态变量,同时手动设置 KVO
在系统提供的状态的基础上,又添加了一些新的状态,如 EvaluatingConditions
, Pending
等,这些状态的改变都需要触发内置状态的 KVO,如 isExecuting
, isFinished
, isReady
等。通常的做法会是这样:
[self willChangeValueForKey:@"isExecuting"]; _state = Executing; [self didChangeValueForKey:@"isExecuting"];
当只有少量的状态改变时,在前后包一层还可以接受,但如果多了的话,就不美观了,这时可以使用 KVO 的一个方法 + keyPathsForValuesAffectingValueForKey:
,它的意思是,哪些 keyPaths 的改变会导致 Key
发生变化。所以可以定义这几个方法,然后正常设置 state
就可以了。
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> { return ["state"] } class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> { return ["state"] } class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> { return ["state"] }
当然,这只是完成了一半,系统知道 state 变了后, isReady
会变,然后就会调用 ready
方法,所以这三个方法我们也要一并覆盖掉。
override var executing: Bool { return state == .Executing } override var finished: Bool { return state == .Finished } override var ready: Bool { switch state { case .Pending: // 省去不相关的代码 if super.ready { // 1 evaluateConditions() } // Until conditions have been evaluated, "isReady" returns false return false case .Ready: return super.ready || cancelled default: return false } }
evaluateConditions
方法会被触发,这里包含了该 Operation 的所有 Conditions 的 evaluateForOperation
的执行结果。 执行 conditions 的 evaluateForOperation
方法
private func evaluateConditions() { assert(state == .Pending && !cancelled, "evaluateConditions() was called out-of-order") state = .EvaluatingConditions // 1 OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in self._internalErrors.extend(failures) self.state = .Ready } }
evaluateForOperation
方法,然后将错误保存在 _internalErrors
里,同时将当前的状态设置为 .Ready
。 或许你会问,如果出现错误,是不是表示条件不满足,如果条件不满足,为什么还要将状态设置为 .Ready
? 这是因为当状态设置为 .Ready
后,就会执行 main
方法,在那里会对 _internalErrors
做统一判断。
override final func main() { assert(state == .Ready, "This operation must be performed on an operation queue.") if _internalErrors.isEmpty && !cancelled { state = .Executing // 1 for observer in observers { observer.operationDidStart(self) } execute() } else { finish() } }
添加 Observers
observer 的实现还是比较简单的,首先定义一个 Protocol,所有的 observer 都需要实现这个 Protocol 里的方法,然后 Operation 内置一个数组作为容器, addObserver
时,将 observer 添加到容器,当处于不同状态时,遍历容器里的 observer,调用相应的方法。
这不免让我们想起了 delegate,跟 delegate 相比,observer 的好处就在于可以指定多个观察者,而 delegate 只能指定一个。
添加 Condtions
跟 observer 的实现思路基本一致。你或许会问,添加的这些 Conditions 什么时候会被触发呢?没错,就是在将 Operation 添加到 OperationQueue 时。
OperationQueue
也是继承自系统的 NSOperationQueue
,同时重写了 addOperation
方法,这个方法主要做了 3 件事
dependencyForOperation
给 Operation 添加 observer
let delegate = BlockObserver( startHandler: nil, produceHandler: { [weak self] in // 1 self?.addOperation($1) }, finishHandler: { [weak self] in if let q = self { // 2 q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1) } } ) op.addObserver(delegate)
处理 Operation 的 dependencies 的 dependencyForOperation
// Extract any dependencies needed by this operation. let dependencies = op.conditions.flatMap { $0.dependencyForOperation(op) } for dependency in dependencies { op.addDependency(dependency) self.addOperation(dependency) }
这个就很简单了,调用 dependencyForOperation
方法,拿到 operation,然后将当前的 op 依赖该 operation,同时将这个 operation 放到 queue 里,所以在 conditions 的 operations 执行完之前,op 是不会执行的。
处理 Operation 的 dependencies 的排他性
let concurrencyCategories: [String] = op.conditions.flatMap { condition in if !condition.dynamicType.isMutuallyExclusive { return nil } return "/(condition.dynamicType)" } if !concurrencyCategories.isEmpty { // Set up the mutual exclusivity dependencies. let exclusivityController = ExclusivityController.sharedExclusivityController exclusivityController.addOperation(op, categories: concurrencyCategories) op.addObserver(BlockObserver { operation, _ in exclusivityController.removeOperation(operation, categories: concurrencyCategories) }) }
在这里可能看不出「排他」的实现,因为是在 exclusivityController
里面实现的,调用了它的 addOperation
方法后,它会去查看这个类型的数组是否为空,如果不为空,就让这个 operation 依赖数组的最后一个。这样在之前的 operation 执行完之前,这个 operation 是不会被执行的。
有了 Operation 和 OperationQueue 之后,就可以开始生产 megabot 了,来看一个「查看原网页」的 Operation,这个 Operation 的作用就是展示传入的 URL。
import Foundation import SafariServices /// An `Operation` to display an `NSURL` in an app-modal `SFSafariViewController`. class MoreInformationOperation: Operation { let URL: NSURL init(URL: NSURL) { self.URL = URL super.init() // 1 addCondition(MutuallyExclusive<UIViewController>()) } override func execute() { dispatch_async(dispatch_get_main_queue()) { self.showSafariViewController() } } private func showSafariViewController() { if let context = UIApplication.sharedApplication().keyWindow?.rootViewController { let safari = SFSafariViewController(URL: URL, entersReaderIfAvailable: false) safari.delegate = self context.presentViewController(safari, animated: true, completion: nil) } else { finish() } } } extension MoreInformationOperation: SFSafariViewControllerDelegate { func safariViewControllerDidFinish(controller: SFSafariViewController) { controller.dismissViewControllerAnimated(true) { // 2 self.finish() } } }
ViewController
相关的 Operation,所以其他同类型的 Operation,需要等我完成后才能被执行。 finish
方法。 如果需要的话,可以给这个 Operation 再加一个 ReachabilityCondition
,当没有网络时就不打开了。
再来看看在 VC 层面的使用。
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { // 1 let operation = BlockOperation { self.performSegueWithIdentifier("showEarthquake", sender: nil) } operation.addCondition(MutuallyExclusive<UIViewController>()) // 2 let blockObserver = BlockObserver { _, errors in /* If the operation errored (ex: a condition failed) then the segue isn't going to happen. We shouldn't leave the row selected. */ if !errors.isEmpty { dispatch_async(dispatch_get_main_queue()) { tableView.deselectRowAtIndexPath(indexPath, animated: true) } } } operation.addObserver(blockObserver) // 3 operationQueue.addOperation(operation) }
NSBlockOperation
, BlockOperation
也可以快速生成一个 Operation。 BlockObserver
也是一个快速生成 observer 的方法,这里描述了当 Operation 完成后的处理。 相比起正常的调用,还是会多了些步骤。
基于 Operation 来架构的思想还是蛮新颖的,可以将复杂的任务拆分成粒度更细的 Operation,然后再组装。但实际使用起来也会有不少问题,比如之前提到的写起来会复杂些,调试时看 backtrace 会很累,不确定是否会带来更好的可维护性等等。不过既然苹果都已经把它用到了线上的 App,至少说明是可行的,至于与已有的架构相比会带来怎样的提升,可能需要实际写起来才知道。
--EOF--
若无特别说明,本站文章均为原创,转载请保留链接,谢谢