NSOperation的高级用法
唠嗑
最近开始迁移文章,把以前写的一些文章都迁移到简书,也算开始在简书安家了。
前言
本文是对([WWDC 2015 Session 226: Advanced NSOperations]) 的一个小结,在这个视频中介绍了用NSOperation来组织App中的功能结构,使逻辑更清晰。如果没有看过这个视频可以看看视频,有助于加深对operation的理解。这篇文章更多的是一些理论知识,没什么代码,但是理解一些内部原理有助于平时我们去看第三方库,比如 SDWebImage AFNetworking YYImage等里面对operation自定义的运用。
1.NSOperation 和NSOperationQueue简介
NSOperationQueue是高级的dispatch_queue_t,但同时也提供了一些功能特性供开发者使用。
1.cancelAllOperations:可以取消队列中所有还未执行的operation
2.maxConcurrentOperationCount:最大并行并发数
我们来看一张图
image如果我们设置maxConcurrentOperationCount=2,我们可以将其看成一个并行队列,同一时刻允许最大的并发数为2。只有上一个operation做完后,下一个operation才会开始。maxConcurrentOperationCount的默认值为default,意思是系统会开设适当数量的子线程。
NSOperation的特点
- 可以cancel未执行的operation
- 指定相同或者不同队列的依赖关系
- kvo监控NSOperation的对象属性
- 指定操作优先级
- 重用operation:operation只能被执行一次后生命周期就结束了,不能被多次执行。
NSOperation可以看成dispatch_block_t的封装。我们来看看operation 的生命周期(图1),当我们init一个operation,它的初始状态是Pending,然后在某一时刻会到Ready,这时operation从queue中拿出开始执,进入executing状态,最后执行完毕到finished状态。在除finished以外的其它三个状态都可以到cancelled状态。
图1
operation各个状态说明:
Ready:一个operation只有在Ready状态才可以被queue执行。下图,在一个串行的queue中,即使第一次进入Ready状态的operation是在第四个被放进去的,但是还是第一个执行。这样的好处就是,放入串行队列的operation我们不需要在operation定义的时候就确定它的执行顺序,而是在后面动态的确定执行顺序。(控制进入Ready状态的时机)
image
用这个Ready属性我们可以做什么事儿呢?我们可以确定依赖(dependencies)。默认条件下,如果A依赖于B,当A进入finish状态,那么B才会进入Ready状态。这个依赖可以存在于不同的operationQueue。一个operaion直有它依赖的所有operation处于finish状态,它才回进入ready状态。如果我们用queue来管理operation,operation在加入到queue中的某一个时刻,它会在它依赖的operaions执行完毕(或者我们没有设置依赖),在某一个时刻自动进入Ready状态并被执行。当然我们可以手动执行operation,但是官方不推荐,因为调用一个未Ready状态的operation会导致异常抛出。
Cancel:可以cancel未执行的operation,该方法会设置对象内的标志位,表明operation不需要执行,如果operation已经start,就不能取消了。operation进入cancel状态只是一个Bool值的翻转,当我们继承operation,我们要自己决定cancel时候需要做什么业务。比如我们在执行一个网络请求,cancel状态代表着cancel 一个urlSession。当我们请求一个数据库,cancel代表我们停止对数据库进行读写操作。所以我们最好KVO这个属性,然后做适当的逻辑。加入到queue的operaion如果不处于finish状态,可以进入cancel状态。调用[operaion cancel]。默认条件下,operation调用cancel,也会标志它进入finished状态。因为只有这样依赖它的operation才有机会被执行。如果我们自定义operation,我们要考虑它进入cancel状态或者这个operation没有被成功执行时候,根据业务是否应该让它进入finished。
KVO:operation 的状态是满足KVO的,可以被监听,可以被监听的keyPath(isCancelled,isAsynchronous,isExecuting,isFinished,isReady等)。注意:因为operaion可能在任何线程内被执行,如果我们将UI元素和以上属性做绑定,那么KVO过来的行为就可能发生在任何的线程内。保险的做法是:如果KVO得知通知后要做UI的事情,都扔到主线程中做。如果我们在自定义operaion时候重写以上属性,也要确保它们可以被KVO和KVC。
线程安全:系统提供的operaion的子类都是线程安全的,所有用系统的operation我们不需要加锁,当我们自定义operaion,提供一些对外的数据存取的方法自己加锁。
同步和异步的Operaions:
如果我们手动执行一个operation,而不是把它放入一个operationQueue进行管理。operation自己本身也有同步和异步之分,(默认同步)。同步的operation不会开子线程,会在调用start这个方法的线程内执行。而异步的会自己开启子线程,如果我们用queue来管理operaion,我们希望它是同步的,因为queue自己会开子线程,我们只要保证加入queue的operation可以按次序执行就行。
如果我们不想用queue和GCD来管理operation但想要利用子线程执行operation,我们应该自定义一个异步的operation。我们需要追踪operation的状态而且手动触发KVO。或者用KVC触发KVO。其实我们平时在用点语法用系统的setter的时候,它是通过KVC设置的,所以自动会触发KVO通知,如果我们重载了setter,为了让属性符合KVO条件,必须手动触发通知。
image作为抽象类,我们可以继承NSOpration,实现自己的operation来代码业务逻辑。
同步的operation条件:
1.重载main方法,这个方法里面放主业务逻辑。也需要查看cancel属性
2.如果我们重载了getter和setter,必须确保调用是线程安全的。
异步operation条件:
1.重载start方法,和asynchronous,exciting,finished属性
start方法中我们必须确保operation异步执行,在此方法中我们需要改变executing更新状态,发送executing KVO通知。当结束operation。必须更新isExecuting和isFinished状态,并触发kvo通知。当cancel一个operation时候,我们也要更新isFinished状态,即使此时operation还未执行。在queue 中的operation必须进入cancel状态后才可以被从operation中移除,
注意:start里面不能调用[super start];在自定义异步operation中,我们完全自定义start,已经全部模拟了父类默认的start行为(start task && send KVO),在start方法里面,我们还要查看isCanceled属性,确保start task前,task是不是已经被取消。如果我们自定义了dependency,我们还需要发送isReady的KVO通知。