Operation概览:
Operation是一个抽象类,不能直接使用,需要继承Operation实现一个子类才能使用,但是系统已经帮我们实现了一个子类BlockOperation,NSInvocationOperation在Swift4里已经去掉了。尽管Operation是抽象类,但是他已经为我们实现了和系统对象的交互,我们只需要添加任务到对象就行,其他的都已经被Operation实现了,无需关心。
一个Operation对象只能执行一次,然后就废了。一般来说,我们需要把Operation添加到OperationQueue对象里,OperationQueue会自动取出Operation对象来执行,OperationQueue执行任务有两种方式,直接在线程上执行,或者调用GCD,一切都由系统来完成,无需关心。
如果你不想添加到OperationQueue里,可以调用Operation的start()方法来开启任务,直接开启任务会有额外的负担,因为执行未就绪的Operation可能会引起异常。Operation的就绪状态由只读属性isReady来标记。所以我们不会直接执行Operation,而是添加到OperationQueue里。
Operation的依赖关系:
依赖关系可以决定执行顺序,可以通过addDependency(:)和removeDependency(:) 来添加和删除依赖。被依赖的Operation先执行,比如op1依赖op2,op1在op2没有完成之前是不会执行的。一点op2执行完,op1就会进入就绪状态,并且可以执行。
op1并不关心op2是否已正确完成,当op2里发生错误,将op2标记为已完成,op1一样可以开始执行。这时候就需要手动追踪错误,来决定是否继续op1。
Operation符合KVO的属性:
Operation有几个属性可以进行KVO,如果需要,可以用KVO来监控:
- isCancelled
- isAsynchronous
- isExecuting
- isFinished
- isReady
- dependencies
- queuePriority
- completionBlock
虽然可以KVO,但是观察者不能用户界面类,比如UIView,因为UI界面必须在主线程中使用,Operation却可能在任何线程中执行。
如果你自定义一个Operation子类来实现上面的属性,必须让他们遵从KVC和KVO,如果添加了新的属性,最好也遵守KVC和KVO。
Multicore Considerations:
Operation本身是多线程安全的,不需要加锁来同步线程操作。如果你自定义子类,在子类里添加的方法必须要保证多线程安全。
同步和异步:
如果你不把Operation添加到OperationQueue里,而是手动开启操作,你可以指定操作是同步执行还是异步执行。默认情况下,操作是同步的。在同步操作中,操作对象不会创建一个单独的线程来运行它的任务。当您直接从代码调用同步操作的start()方法时,操作将立即在当前线程中执行。当该对象的start()方法将控制权返回给调用者时,任务本身就完成了。
当你调用异步操作的start()方法时,该方法可能在相应任务完成之前返回。异步操作对象负责将其任务安排在一个单独的线程上。该操作可以通过调用异步方法或将一个任务提交到GCD来执行,从而直接启动一个新线程。当控制权返回给调用者时,操作可能还在执行。
如果你用OperationQueue来执行Operation,想要同步很简单。但是如果手动操作Operation,想要异步却很麻烦。你需要用KVO来监听任务状态。所以不要直接使用Operation开启操作,用OperationQueue可以轻松控制Operation的同步和异步。
当把Operation添加到OperationQueue时,会忽略Operation的isAsynchronous的属性,并且总是开启新线程来调用Operation的start()方法。因此,isAsynchronous属性在此时就没用了。
子类化时的注意点:
Operation仅提供了跟踪操作状态的逻辑,具体执行代码的方式,需要子类来定义。怎么定义子类,取决于同步或者异步来执行任务:
需要重载的方法:
如果同步执行,只需要重载一个方法,就是:
- main():在这个方法里执行你的任务。需要定一个初始化方法,以便容易初始化一个自定义类。可能还需要一个getter和setter方法来获取操作的数据,如果你实现了这两个方法,就要保证他们是多线程安全的。
如果你需要定义异步操作,还需要实现下面的方法和属性:
- start()
- isAsynchronous
- isExecuting
- isFinished
在并发操作中,start()方法负责以异步方式启动操作。无论是开启新线程,还是调用异步函数,都是在这个方法里执行的。启动操作后,start()方法还要更新isExecuting属性。可以通过KVO来通知观察者isExecuting的改变。isExecuting属性必须要线程安全。
在完成它的任务或取消,你的并发操作对象必须生成KVO通知双方执行和完成关键路径为您的运行状态的最终变化标志。(在取消的情况下还是更新是关键路径,重要的即使操作没有完全完成它的任务。队列操作必须报告,他们才可以从队列中移除了。)除了生成KVO通知,你的执行和完成的覆盖性能也应该继续报告准确的值取决于你的工作状态。
在完成任务或者取消任务时,你的并发operation对象会生成isExecuting和isFinished的KVO通知,来标记operation的最终状态。(在取消的情况下,即使任务没有完成,更新isFinished状态也是必要的。在queue里排队的operation必须在从queue里移除之前报告isFinished的状态)除了生成KVO通知,覆盖的isExecuting和isFinished属性也应该准确的记录operation的状态的准确值。