本文翻译matt文章NSOperation
在现实生活中,有很多工作要做。每一天都有源源不断的任务填充在我们工作的时间。
然而不管我们个人要做的工作是多么繁重,但是比起一个iOS程序的工作量来说却显得苍白无力。因为一个iOS应用程序中有数百万条指令要做,它们每隔16毫秒都设法画一个框架。
不管在生活还是程序中,生产力都是能够影响调度,优先级,多线程保持常态的一个重要因素。
为了使app能够顺畅运行的方法就是将一些不必要的工作尽可能的放在后来来执行。为了这一方案,当带的Cocoa开发者有两条路选择:GCD和NSOperation。这篇文章主要讲解的是NSOperation。
NSOperation表示单个独立的计算单元,作为一个抽象类,它给子类提供一个十分有用而且线程安全的方式来建立状态、优先级、依赖关系和取消等的模型。
在并不需要创建一个自定义的NSOperation子类的情况下,Foundation框架提供了NSBlockOperation和NVInvocationOperation的具体实现。
适用于使用NSOperation任务的例子很多,比如:网络请求,图片缩放,文本处理以及者其他诸如重复性,结构化或产生相关状态和数据的长时间运行的任务。
但是如果简单的将一个计算包装成一个对象而不做其他处理显然没有太大用处。这就是NSOperation的来源。
NSOperationQueue
NSOperationQueue控制操作的并发执行。它表现为一个"粗糙"的FIFO队列。但是为什么说是粗糙呢,是因为在这个队列中那些高优先级的(NSOperation.queuePriority)会跳到那些低优先级队列之前。NSOperationQueue同样能够控制在一个给定时间段内可并发执行操作的数量。使用maxConcurrentOperationCount
这一属性控制。
NSOperationQueue自身是受GCD支持的,尽管其有自身的详细实现。
开始一个NSOperation队列,要么调用start方法,要么将其加入一个NSOperationQueue,在NSOperationQueue中一旦其到达了队列最前端就会自动开始执行。既然NSOperation的众多优点都是由NSOperationQueue衍生而来,大多数情况下我们应该首选将一个操作加入队列中来调用而取代直接调用start方法来启动一个操作。
State
NSOperation包含了一个非常优雅的状态机来描述每一个操作的执行:
isReady -> isExecuting -> isFinished
State是通过基于那些keypath的KVO通知隐晦的决定的。当一个队列准备好执行,它为isReady的keypath发送一个KVO通知(isReady的keypath相应的属性会返回YES)。
每一个属性对于其他的属性必须是互相独立不同的,也就是同时只能够有一个属性返回YES从而才能维护一个连续的状态。
- isReady:返回YES表示这个队列已经做好要去执行的准备。返回NO表示它的某些先前相关步骤还未完成。
- isExecuting:返回YES如果该操作在其自己的任务上执行。反之返回NO。
- isFinished:如果操作的任务成功完成或者操作被取消 就会返回YES。NSOperationQueue直到它管理的所有操作的isFinished属性全标为YES以后操作才停止出列。所以为了避免死锁情况发生,应该在子类中正确的实现它。
Cancellation
通过尽早的执行cancel操作来阻止那些无用的工作执行是很有用的。通常取消的情况是:操作失败,用户主动明确要取消。
与执行状态类似,NSOperation通过KVO通知isCancelled的keypath上发送取消操作修改isCancelled属性的返回值。当一个操作取消了,那么就应该清空所有的内部细节并到达一个恰当的最终状态,当然这一操作是越快越好的。这里明确的要指出,这个时候isCancelled和isFinished的值都是YES,而isExecuting的值是NO。
要特别注意“cancel“这个词的拼写。对于Operation来说:1.cancel表示一个函数 2.cancelled表示属性。
Priority
并不是所有的操作都是有同样的重要性。通过设定queuePriority属性的值能够促进或推迟在NSOperationQueue中Operation的执行顺序。
NSOperationQueuePriority定义如下:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHight = 8
};
此外有些操作还可以指定threadPriority的值,它的取值范围可以从0.0-1.0。鉴于queuePriority属性决定了操作执行的顺序,threadPriority则制定了当操作开始执行以后的CPU计算能力的分配。
Quality of Service
Queality of Service 是 iOS8 &OS X Yosemite出现的一个新的概念,它为调度系统创造一致的高层语义。
对于NSOperation来说,为了支持这一新的qualityOfService属性,threadProperty这一属性便废弃了。
服务级别根据CPU,网络和磁盘的分配来创建一个操作的系统优先级。一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
下面的枚举用以表示一个操作的性质和紧迫性。
我们鼓励应用程序选择最合适的值用以操作,以确保一个良好的用户体验。
NSQualityOfService
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
- .UserInteractive:UserInteractive QoS 用于直接参与提供一个交互式UI,如处理事件或对屏幕的绘制。
- .UserInitiated:UserInitiated QoS用于表示执行工作已经被用户显示提出并且要求结果能够立即展示以便进行进一步的用户交互。比如:用户在信息列表点击之后立即加载一个email。
- .Utility:Utility QoS用于表述执行一项工作后,用户并不需要立即得到结果。这一工作通常用户已经请求过或者在初始化的时候已经自动执行,不会阻碍用户用户的进一步交互,通常在用户可见的时间尺度和可能由一个非模态的进度指示器展示给用户。
- .Background:Background QoS用于那些非用户初始化或可见的工作。通常说来,用户甚至不知道这想工作已经发生,但是它会以最有效的方法运行同时会树丛那些高优先级的QoS。例如:内容抓取,搜索索引,备份,同步与外部系统的数据。
- .Default:默认的QoS表明QoS信息缺失。尽可能的从其它资源推断可能的QoS信息。如果这一推断不成立,一个位于UserInitiated和Utility之间的QoS将得以使用。
NSOperation *backgroundOperation = [[NSOperation alloc] init];
backgroundOperation.queuePriority = NSOperationQueuePriorityLow;
backgroundOperation.qualityOfService = NSOperationQualityServiceBackground;
[[NSOperationQueue mainQueue] addOperation:backgroundOperation];
Asynchronous Operations
iOS8之后另一变化是弃用并发而偏向于asynchronous异步属性。
最原先时候,并发属性concurrent适用于区分这样的操作:在一个主线程中执行所有工作的操作 ,异步执行时候管理其自身状态的操作。这一属性用于决定NSOperationQueue是否在一个单独的线程中执行一个方法。当改变NSOperationQueue来运行一个内部调度队列而不是直接管理线程之后,其这一特性就被忽略了。新的异步属性清理了并发语意如同蜘蛛网般的混乱。现在新的asynchronous属性作为唯一能够决定NSOperation在主程序中去同步还是异步执行的属性。
Dependencies
基于一个应用程序的复杂性来看,将一个大的任务分成一系列可组合的子任务是有重大意义的。通过NSOperation 依赖可以实现这一方案。
举例说来:描述一个从服务端下载并缩放图片这个进程,我们可能将网络分为一个操作,缩放作为另一个操作。(这样做是为了再次利用网络操作下载其他的资源,或使用缩放操作来对其他缓存过的图片进行操作)然而,如果图片没有下载完成,那么缩放工作是不能尽兴的,那么我们就认为网络操作是缩放操作的一个依赖,必须在缩放操作开始之前完成。
用代码表示:
NSOperation *networkingOperation = ...;
NSOperation *resizingOperation = ...;
[resizingOperation addDependency:networkingOperation];
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
一个操作直到它的所有依赖的isFinished状态返回YES才会开始。
注意一定不要创建一个循环依赖,A依赖B,B依赖A,这样会造成死锁。
CompletionBlock
当一个NSOperation完成后,它会完整的执行一次它的completionBlock.这为视图控制器或模型提供了一个很好的方法来定制一个操作的行为。
NSOperation *operation = ...;
operation.completionBlock = ^{
NSLog("Completed");
};
[[NSOperationQueue mainQueue] addOperation:operation];
NSOperation对iOS、OS人员来说是至关重要的。而GCD是一个理想的内联的异步线程,NSOperation提供一个更全面的,在一个应用程序中封装所有数据结构,重复任务的面向对象的模型。
开发人员应该对任何给定问题,重复性工作使用使用高级别的抽象问题来解决。这一抽象就是NSOperation。
When to Use GCD
调度队列,组,信号量,来源组成并发的原语,在这之上,系统框架得以建立。对于一一次性计算或者加速现有的方法,通常使用轻量级的GCD。
When to Use NSOperation
如果对服务质量和特性的队列优先级有依赖那么可以使用NSOperation。与GCD队列中安排好的块不同,一个NSOperation能够被取消并有其自身的状态查询码。通过子类化,NSOperation可以将工作结果与其自身结合起来用于后期查询参考。
由于本人水平有限,翻译不足,敬请指出。谢谢