一个抽象类,表示和单任务相关的代码和数据;
继承自NSObject;
概述
因为NSOperation类是一个抽象类,我们不能直接使用它,而是要使用其子类,或者使用系统定义的子类 (NSInvocationOperation 或者 NSBlockOperation) 来执行任务。尽管NSOperation类是一个抽象类,但还是它的基本实现已经包含了调度任务安全执行的重要逻辑。这个内置逻辑的存在允许我们集中精力去关注任务的实际实现,而不是去关注任何和其他系统对象正确工作所需要的粘合代码。
一个NSOperation对象,是一个单发对象。也就是说,它只会执行一次任务,而不会多次执行任务。通常,我们通过添加任务到 operation queue 中来执行NSOperation对象。operation queue 可以直接在辅助线程上运行操作,也可以在libdispatch库(如GCD)上间接的执行操作。关于队列怎么执行操作的更详细的信息可以看看NSOperationQueue。
如果你不想使用 operation queue ,你可以直接从代码中调用start方法来执行操作。而手动执行操作会给代码增加更多的负担,因为启动一个未处于ready状态的操作会触发异常。ready属性报告了操作的准备情况。
operation 依赖
依赖关系是按照特定顺序执行操作的一种便捷的方法。我们可以使用 addDependency:方法和 removeDependency: 方法来增加或者删除操作的依赖。默认情况下,具有依赖项的操作对象在所有依赖
的操作对象完成执行前不会被认为已经准备就绪。但是当最后一个依赖操作完成,操作对象就会进入准备就绪状态,并且可以执行。NSOperation所支持的依赖项并不区分依赖项操作是否成功完成。(换句话说,取消操作同样被视为完成。)在依赖项操作被取消或者没有成功完成任务的情况下,是否继续执行具有依赖项的操作取决于你。这也许就需要我们添加一些额外的错误追踪功能到操作对象中。
符合KVO的属性
NSOperation类中的几个属性是符合键值观察KVO和键值编码KVC的。根据需要,我们可以观察这些属性来控制程序的其他部分。要观察这些属性,请使用一下的键路径:
• isCancelled - 只读
• isAsynchronous - 只读
• isExecuting - 只读
• isFinished - 只读
• isReady -只读
• dependencies - 只读
• queuePriority - 可读可写
• completionBlock - 可读可写
尽管可以将观察者附加到这些属性上,但是我们不应该使用cocoa绑定来将它们绑定到程序中的UI控件中。因为与UI相关的代码通常都是在主线程中执行的,而operation可以在任何线程中执行,因此与其相关的KVO通知也同样可能出现在任何线程中。
如果为前面的属性提供了自定义的实现,那么这些实现也必须要保持遵从KVC和KVO。如果为NSOperation对象定义了其他的属性,那么建议你为这些属性做好KVC和KVO的兼容。
关于多核的思考
NSOperation类本身可以感知多核。因此,在没有创建额外的锁来同步该对象的访问时,从多个线程调用NSOperation对象的方法是安全的。这种行为是必要的,因为一个操作通常是在和创建并监控它的线程独立的另一个线程中执行。
当我们写一个NSOperation的子类时,我们必须确保任意一个覆写的方法都是多线程调用安全的。如果我们在子类中实现了自定义的方法,比如自定义数据访问其,我们也必须确保这些方法是线程安全的。因此,在操作中访问任意数据变量必须是同步的,来防止可能的数据损坏。
异步与同步操作
同步:
如果我们打算手动的执行NSOperation对象,而不是把它添加到队列中,那么我们应该设计我们的NSOperation 以同步或者异步的方式执行。NSOperation对象默认是同步的。在同步的操作中,操作对象不能创建和其执行任务分离的其他的线程。当我们直接从代码中调用了同步操作的start方法后,操作会立刻在当前线程中执行。当这个对象的start方法将控制权返回给调用者时,任务本身就完成了。
异步:
当我们调用异步操作的start方法时,在对应的任务完成之前可能会返回。异步NSOperation对象负责在一个独立的线程中调度任务。NSOperation对象可以通过直接开启一个新的线程、通过调用异步方法、或者提交一个block给执行分发队列来完成。当控制权返回给调用者时,操作是否正在进行实际上并不重要,重要的是它可能正在进行。
如果我们打算使用队列来执行操作的话,那么定义操作为同步的会更简单。但是,如果我们手动执行操作,那么可能希望将操作对象定义为异步的。定义异步操作需要更多的工作,因为您必须监控任务的当前状态,并且使用KVO通知来报告该状态下的改变。但是在想确保手动执行操作不会阻塞调用线程的情况下,定义异步操作是有用的。
当我们给operation queue中添加operation 时,队列会忽略 asynchronous 属性,并总是从独立的线程中调用start方法。因此,如果我们通过添加operation 到operation queue中来执行operation,那么就没有理由使operation异步。
关于如何定义同步和异步操作的详细信息,请看子类注释
子类注释
NSOperation类提供了基本的逻辑来追踪operation的执行状态,但是除此之外,必须对其进行子类化才能完成一些实际的工作。如何创建子类取决于操作是并发执行还是非并发执行。
待覆写的方法
对于非并发的操作,通常只需要覆写一个方法:
* main
在这个方法中,我们可以放置需要执行给定任务的代码。当然,我们也应该定义一个自定义的初始化方法来更容易的创建自定义类的实例。我们也可以定义getter/setter方法来访问operation的数据。然而,如果确实定义了getter/setter方法,那么我们应该确保这些方法是多线程调用安全的。
如果我们创建了一个并发的操作,那么至少需要覆写下面的方法和属性:
* start
* asynchronous
* executing
* finished
在并发操作中,start方法负责以异步的方式开启操作。无论我们是生成一个线程还是调用一个异步函数,都可以通过这个方法来实现。当启动操作时,start方法还应该更新有executing属性报告的操作的执行状态。我们可以通过发送执行键路径的KVO通知来实现,这样可以使关注的客户们知道操作现在正在运行。executing属性必须以线程安全的方式来提供状态。
当任务完成或者取消时,并发操作对象必须为 isExecuting 和 isFinished键路径生成KVO通知来标记操作的最终状态改变。(在取消的情况下,更新isFinished键路径仍然是非常重要的,技术操作没有完全完成任务。队列操作必须在他们从队列中移除之前报告它们已经完成。)除了生成KVO通知外,我们对 executing 和 finished属性的覆写也应该继续报告基于操作状态的准确的值。
注意:
在开始方法中,任何时候都不能调用super。当我们定义一个并发操作时,我们自己会提供和默认start方法提供的相同的行为,包含启动任务和生成适当的KVO通知。start方法也会检查操作是否在启动任务前被取消。
即使对于并发操作,也没有必要覆写除了上述方法之外的其他方法。但是,如果我们自定义操作的依赖特性,那么就必须覆写其他方法并提供KVO通知。在依赖项的情况下,这可能只需要为isReady键路径提供通知。因为dependencies属性包含了依赖操作列表,所以对其的更改已经由默认的NSOperation类处理了。
维护NSOperation对象的状态
NSOperation对象在内部维护状态信息,以确定何时可以安全执行,并通过操作的生命周期来通知外部客户端进程。自定义的子类维护状态信息以确保代码中的操作正确执行。和操作状态相关的键路径为:
isReady
isReady键路径让客户端知道操作何时准备就绪可以执行。ready属性在操作现在准备就绪时包含true值,在其所依赖的操作项未完成时则包含false值。
在大多数情况下,我们不必管理这个键路径的状态。但是,如果操作的准备就绪是由依赖操作以外的其他因素,如一些程序中的外部条件,决定时,那么我们可以自己提供ready属性的实现并自己追踪操作的准备就绪状态。通常只在外部状态允许时创建操作对象会更简单。
在MAC OS10.6及以后,如果在等待一个或者多个依赖操作完成时,取消了一个操作,这些依赖项随后会被忽略,并更新这个属性的值,来反映现在准备就绪可以运行了。这个行为使操作对垒可以优惠更快的从队列中清除已经取消的操作。
isExecuting
isExecuting键路径可以让客户端知道操作是否正在积极地处理所分配的任务。如果操作正在处理其任务,executing属性必须报告true值,否则报告false值。
如果我们替换了NSOperation对象的start方法,那么还必须替换executing属性并且在NSOperation对象的执行状态发生改变时,生成KVO通知。
isFinished
isFinished键路径可以让用户知道操作是否成功完成任务或者被取消并正在退出。NSOperation对象不会清除依赖直到isFinished键路径的值变为true。同样地,操作队列不会使操作出列,知道finished属性包含true值。因此,标记操作为完成状态对于防止队列备份正在进行中或者已取消的操作至关重要。
如果我们替换了NSOperation对象的start方法,那么还必须替换finished属性并且在NSOperation对象完成执行或者被取消时,生成KVO通知。
isCancelled
isCancelled键路径可以让用户知道请求取消操作。支持取消是自愿的,但是鼓励代码中不必为此键路径发送通知。
响应取消命令
一旦将操作加入队列中,操作就不受我们控制了。队列会接管和处理任务的调度。但是,如果稍后我们不想执行该操作了,因为用户在进度面板中按下了取消按钮或者退出了应用程序,例如我们取消了该操作以防止它不必要的占用CPU的时间。你可以通过调用操作对象的cancel方法或者调用NSOperationQueue类的 cancelAllOperations 方法来实现。
取消操作不会立即强制停止正在执行的操作。尽管在所有的操作中都应该考虑到cancelled属性中的值,但是代码必须显式检查这个属性的值,并根据需要中止。NSOperation 的默认实现包括了对取消的检查。例如,如果我们再start方法调用前取消了一个操作,那么start方法将会退出,而不会启动该任务。
注意:
在MacOS 10.6及更高的版本中,如果对位于操作队列中并且具有未完成的依赖操作的操作调用cancel方法,那么这些依赖操作随后就会被忽略掉。由于操作已经被取消了,因此此行为允许队列调用曹组的start方法来从队列中删除操作,而不调用其主方法。如果我们调用了不在队列中的操作的cancel方法,操作就会立即被标记为取消。在每种情况下,标记操作为准备就绪或者完成状态都会导致生成相应的KVO通知。
在我们写的任何自定义的代码中,都英嘎支持取消语义。尤其是,主任务代码应该定期检查cancel属性的值。如果属性值为YES,那么操作对象应该尽快清理并退出。如果我们事先了自定义的start方法,这个方法应该提前对取消值得检查,并恰当处理。自定义的start方法必须准备好处理这种提前取消。
除了在操作取消时简单退出之外,还必须将取消的操作移动到适当的最终状态。具体来说,如果我们自己管理finished和executing属性的值(可能是因为我们正在实现一个并发操作),那么就必须要相应的更新这些值。具体来说,必须更改finished返回值为YES,executing返回值为NO。即使在开始执行之前就取消了操作,也必须对这些值进行更改。
初始化
- init
返回一个初始化的 NSOperation 对象.
执行操作
- start
开始执行操作.
- main
执行接收者的非并发任务
completionBlock
操作的主任务完成后执行的block
取消操作
- cancel
建议操作对象停止执行其任务.
获取操作的状态
cancelled
表示操作是否被取消的布尔值
executing
表示操作当前是否在执行的布尔值
finished
表示曹组是否完成执行任务的布尔值
concurrent
表示操作是否在异步的执行任务的布尔值
asynchronous
表示操作是否在异步的执行任务的布尔值
ready
表示操作现在是否可以被执行的布尔值
name
操作名称.
管理依赖项
- addDependency:
使接收者依赖指定操作的完成
- removeDependency:
移除接收者对指定操作的依赖
dependencies
在当前对象可以开始执行前必须完成执行的操作对象数组
配置执行的优先级
qualityOfService
授予系统资源操作的相对重要性。
queuePriority
操作队列中的操作的执行优先级。
Waiting on an Operation Object
- waitUntilFinished
阻塞当前线程的执行直到操作对象完成任务
常量
NSOperationQueuePriority
这些常量可以对操作执行的优先级排序。
NSQualityOfService
用于指示工作对系统的性质和重要性。当存在资源竞争时,具有较高服务质量的类的工作会获得比较低服务质量的类更多的资源。