1). NSOperation是基于GCD之上的更高一层封装, 拥有更多的API (e.g. suspend, finished, cancel等等), 自定义更加灵活。
2). 在NSOperationQueue中, 可以指定各个NSOperation之间的依赖关系. 可以跨线程 跨队列添加依赖 ,可指定队列中任务的执行顺序
3). start 方法中默认实现了 用KVO可以方便的监测NSOperation的状态(isExecuted, isFinished, isCancelled). 当自定义实现一个并发的 NSOperation 子类时,需要手动触发kvo
4). 更高的可定制能力, 继承NSOperation实现可复用的逻辑模块. 2种系统提供的子类 和 队列queue的配合使用
4). 设置最大并发数 ,控制任务的执行方式,类似串行 和 并发。默认是串行的,不加入 queue时
NSOperation 是个抽象类,不能用来封装操作。只有使用它的子类来封装操作。
1: 使用子类 NSInvocationOperation,系统提供
2: 使用子类 NSBlockOperation,系统提供
3: 自定义继承自 NSOperation 的子类。通过实现内部相应的方法来封装操作
在不使用 NSOperationQueue,单独使用 NSOperation 的情况下,系统同步执行操作。
1). 串行(Serial) VS. 并行(Concurrent)
串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.
2). 同步(Synchronous) VS. 异步(Asynchronous)
同步和异步描述的其实就是函数什么时候返回. 比如用来下载图片的函数A: {download image}, 同步函数只有在image下载结束之后才返回, 下载的这段时间函数A只能搬个小板凳在那儿坐等... 而异步函数, 立即返回. 图片会去下载, 但函数A不会去等它完成. So, 异步函数不会堵塞当前线程去执行下一个函数!
平常说的并发编程包括狭义上的"并行"和"并发", 不能保证代码会被并行执行, 但可以以并发的方式设计代码. 系统会判断在某一个时刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)执行, 否则就用context switch来分时并发(concurrency)执行.
NSOperation可以独立执行(直接调用[operation start]), 也可以添加到 NSOperationQueue里面执行, 添加到队列里会主动执行,不用再执行start 。两种情况下是否并发执行是不同的.
1. 独立执行的NSOperation
NSOperation默认是非并发的(non-concurrent), 如果 把operation放到某个线程执行, 它会一直block住该线程(同步执行,阻塞线程,任务完成才返回), 直到operation finished.
对于非并发的operation 只需要继承NSOperation, 重写main()方法就可以了。 例如实现一个下载需求:
NSOperation默认是非并发的:NSOperation对象默认按同步方式执行, 在调用start方法的那个线程中直接执行。
main函数执行完成后, 默认 isExecuting会被置为NO, 而isFinished则被置为YES.
为什么在并发情况下需要自己来控制 isExecuting和isFinished 这两个状态量呢?
因为在并发情况下系统不知道operation什么时候 finished, operation里面的task一般来说是异步执行的, 也就是start函数返回了operation不一定就是finish了。 因为异步函数不等待,直接返回,可能block 里的任务还在执行
什么时候将isFinished置为YES(主动触发相应的KVO消息), operation就什么时候完成了.
NSOperation对象的isConcurrent方法会告诉我们这个操作相对于调用start方法的线程,是同步还是异步执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行
怎么实现并发(concurrent)的NSOperation呢?
1). 重写isConcurrent函数, 返回YES, 这个告诉系统各单位注意了我这个operation是要并发的.
2). 重写start()函数. 在start方法中可以做判断如果isfinished就return,防止多次调用。 可以多次调用 isCancelled 判断当前任务是否被取消。
3). 重写isExecuting和isFinished 函数 , 调用 willChangeValueForKey / didChangeValueForKey 主动 触发KVO消息.
4 》:需要自己创建自动释放池,操作结束就自动释放。
重写main和重写start 区别?
重写main不用关心operation对象的释放问题,不要处理任务状态。
只重写main方法,并且不设置 self.taskFinished = YES时,Operation方法都是可以正常执行的,也就是说main执行结束时系统将线程的 isFinished置为YES了,其余任务可对线程进行重用。
重写start方法,需要在start方法或者main方法中对finished赋值为yes,operation对象才会释放。 不然导致之后的任务无法重用当前线程。
注意:
1: 同时重写start和main方法时,start方法优先执行,main方法不会被执行;
2: 如果只重写main方法,则main方法会被执行。
3: 如果自定义了dependency 依赖的相关操作,还需要发送isReady的KVO通知。
暂停,依赖,取消 操作
暂停操作: NSOperationQueue 的对象属性suspended设置为YES,队列会停止对任务调度。
对那些还在线程中的操作有影响的。如果任务正在执行将不会受到影响,因为任务已经被队列调度到一个线程上并执行。
继续操作:当属性suspended设置为NO会继续执行线程操作。队列将积极启动队列中已准备执行的操作。
队列中的 Operation,只有Operation 标记为已结束 finished 才能被队列移除remove 。
NSOperation实例之间设置依赖关系应该在加入队列之前;
不同的NSOperation实例之间可以设置依赖关系,不同queue的NSOperation之间也可以创建依赖关系 ,但是要注意不要“循环依赖”;循环依赖,就都不会执行。
操作对象添加到NSOperationQueue之后,通常短时间内就会运行。但是如果存在依赖,或者整个队列被暂停等原因,也可能需要等待。
操作对象添加NSOperationQueue中后,不要再修改操作对象的状态。因为操作对象可能会在任何时候运行,因此改变操作对象的依赖或数据会产生无法预估的问题。只能查看操作对象的状态, 比如是否正在运行、等待运行、已经完成等。
注意:只会停止调度队列中未开始执行的 操作对象,正在执行任务的依然会执行,且取消不可恢复。
由于NSOperation是可以cancel的, 所以需要在operation block 任务执行过程中判断当前operation是否已经被cancel了(isCancelled).
如果已经被cancel那就不往下执行了. 当调用了 [operation cancel]后, isCancelled会被置为YES.
cancel 唯一能影响的就是清除 operation 的依赖关系,使其立即可以被执行---处于ready 状态。此时 queue 并不会 remove 其中的 operations。
在队列中未被调度的Operation,会调用start方法或者main 方法中执行操作,以便Operation 对象能处理取消cancel 事件。对于正在线程中执行任务的Operation 对象,会继续执行,该Operation 对象会被标记经结束----修改finished 为yes。
重写一个并发的 NSOperation 必须要实现的方法,要重写start 方法,还有一些 状态属性的设置:
NSOperation是线程安全的,继承的子类的方法实现中要加锁,保证线程安全。
当子类化NSOperation类时, 必须确保任何重写的方法能在多个线程中是安全的调用。
start和main的区别?
main方法的话,如果main方法执行完毕,那么整个operation就会从队列中被移除。如果你是一个自定义的operation并且它是某些类的代理,这些类恰好有异步方法,就会找不到代理导致程序出错了。
start方法你可以控制这个operation的生命周期了,设置 finished 等状态量的值来控制。
关于start方法与main方法的执行时机,可以粗略这样理解:operation加入queque时直接执行main,不调用start;调用start方法时,在start方法中调用main方法。
重写main不用关心operation对象的释放问题,不要处理任务状态, 串行方法重写main , main函数执行完成后, isExecuting会被置为NO, 而isFinished则被置为YES.
为了防止外部多次调用[operation start]方法,重写start方法需要 对 blockoperation 的 isfinished 状态进行判断,如果 isfinished=yes,就不会执行了,必须处理好isExecuting, isFinished, isConcurrent 和 isReady这些属性。
参考:
https://blog.csdn.net/TuGeLe/article/details/80220100 介绍较为详细,并发和非并发的区别以及都需要实现什么方法 。
http://zxfcumtcs.github.io/2016/05/17/NSOperation/ 详述 NSOperation 并发和非并发的区别和实现 易懂
https://www.jianshu.com/p/ebb3e42049fd main 和 start 函数重写区别
https://www.it610.com/article/1276711537133764608.htm main 和 start 函数 区别
https://www.meiwen.com.cn/subject/giwooxtx.html 完整的实现一个 NSOperation 分类都需要实现的方法
https://www.cnblogs.com/blogwithstudyofwyn/p/10011029.html 较长,较为详细,但是比较冗杂,看起来没那么容易懂,但是总结较多
[iOS]NSOperation的start与main,并发与非并发。_wcxdell的专栏-CSDN博客
iOS中NSOperation详解 -
多线程之NSOperation进阶用法 -
自定义NSOperation_MeteoriteMan的博客-CSDN博客