外甥打灯笼---------照旧,上图:
NSOperation定义:
NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlockOperation和NSInvocationOperation这两个实体类。
NSOperation优先级设定:
在iOS 8.0前,通过设置操作的优先级,尽可能的保证某个操作优先处理,随着硬件性能上的提升,通过设置优先级效果已经越来越不明显,在iOS 8.0后,推出了服务质量,通过设置服务质量,让系统优先处理某一个操作。
通过测试,设置最高优先级的任务也确实不一定会比低优先级的任务先执行,这一点可以通过依赖(dency)来解决优先级的问题。
自定义NSOperation:
要继承NSOperation必须重写初始化方法、重写main方法,如文档:
重写NSOperation需要注意的点:
1、如果需要自定义并发执行的 Operation,必须重写start、main、isExecuting、isFinished、isAsynchronous方法。
2、在 operation 的 main 方法里面,必须提供 autorelease pool,因为你的 operation 完成后需要销毁。
3、一旦你的 operation 开始了,必须通过 KVO,告诉所有的监听者,现在该operation的执行状态。
4、调用时,如果需要并发执行 Operation,必须调用performOperation:方法,当然,也可以改为自定义其他方法或者直接在start方法添加多线程调用。
5、对于自定义的 Operation 类,如果不需要并发执行,可以直接调用start方法。
资料出处:https://www.jianshu.com/p/813f7d58935d
```
需要注意的点:
1:如果需要取消某个操作任务,可以调用 [operation cancle]方法。同时需要在重写的main函数中,做相应的移除,终止操作。
2: 一旦操作提交到线程操作队列中,就不能设置某个操作任务的依赖属性等等。比如上边例子中 [operation1 addDependency:operation2]; 放在 [operationQueue addOperation:operation1]; 之前 和之后是完全不同的。放在加入操作队列之前还是能够起作用,放在之后,就不起任何作用。所以别放错位置。
3: 取消了一个操作,它不会马上就发生。它会在未来的某个时候某人在“main”函数中明确地检查isCancelled == YES 时被取消掉;否则,操作会一直执行到完成为止。
这样就会导致一个问题,可能这个操作任务在你调用取消函数之前就返回了,所以看到的情况就是我明明对该操作做了取消,为什么还有返回数据的原因。
4: 挂起一个队列不会让一个已经执行的任务停止.
5: NSOperationQueue并不能将单个的NSOperation进行挂起操作,NSOperation自身也无法将自己暂停后再进行恢复操作,当NSOperation取消了之后,你再也无法对其进行恢复操作了,在NSOperationQueue上,是无法实现的。
```
补充问答:
1、并行和并发的区别?
并行:多个任务同时执行
并发:多个任务每隔一段时间执行
2、串行/并行、同步异步的区别?(附带如何判断GCD的执行顺序、是否开辟线程)
串行:线程按循序执行
并行:多线程同时执行
同步:不开启新线程
异步:开启新线程
3、GCD的执行顺序
1、串行执行,一个挨着一个执行 2、并行执行是没有顺序的,如果设置了barrier栅栏,在栅栏之前的先执行,之后的等之前的执行完毕之后再执行。
是否开启新线程:只要是异步就开启新线程
4、NSOperation与GCD的关系
NSOperation是基于GCD进行封装的,面向对象的。GCD是基于C语言的。
5、默认最大并发
a、NSThread本身并没有限制,也不支持最大并发。
b、NSOperation系统默认的并发数是-1,所有任务全部并发执行。
c、GCD没有默认并发数,我们可以通过信号量来设置并发量。dispatch_semaphore。
6、线程取消
[NSOperation cancel]一旦取消无法再恢复,正在执行的任务无法取消。
7、[thread cancel]可以关闭线程?
不能关闭线程,[thread cancel]只是对线程进行cancel标记。[NSThread exit];才是关闭线程。
资料:https://www.jianshu.com/p/3bb2afebbc27
8、performSelector开头那么多方法为什么分散在不同的文件里?
a、performSelector是运行时系统负责去找函数/方法的,在编译时候不做任何校验;但是使用performSelector的话一定是在运行时候才能发现,此时程序崩溃。
b、但是直接调用肯定在编译是会校验。如果函数/方法不存在,那么直接调用 在编译时候就能够发现(借助Xcode可以写完就发现),
9、NSBlockOperation和NSInvocationOperation有什么关系和区别。
相同点:
a、NSBlockOperation和NSInvocationOperation都是NSOperation的子类。通过子类执行任务,不添加到任务中在主线程中执行任务。
不同点:
a、NSBlockOperation可以在Block中执行任务。通过addExecutionBlock:方法添加更多的操作遵循FIFO。
注意:只要NSBlockOperation封装的操作数>0, 就会异步执行操作。也就是说只要有这个方法就会异步执行。
b、NSInvocationOperation是使用方法执行操作。
c、NSBlockOperation可以解决NSInvocationOperation传递参数受限的问题。
自定义NSOperation:https://www.jianshu.com/p/813f7d58935d
10、NSInvocationOperation如何解决参数受限的问题
a、使用字典
11、NSOperation可以像GCD一样设置串行并行么?
a、串行并行其实是GCD的名词。
并行意味着多线程执行任务、串行意味着单线程执行任务。任务在线程内部都是串行执行De。
b、NSOperation没有串并行的概念,但是我们可以通过设置某个队列的最大并发数为1,让其中任务被自动分配到不同线程中自动执行,以达到串并行的效果。另外,我们也可以设置依赖来实现串行执行的效果。
12、NSOperation队列内操作执行的时间点
a、如果插入的操作存在依赖关系,优先完成依赖操作。
b、如果插入的操作不存在依赖关系、队列并发数为1下,采用先进先出的原则,反之直接开辟新线程执行。
13、NSOperation设置优先级是否可以直接决定操作的执行顺序?
测试描述:通过测试,设置最高优先级的任务也确实不一定会比低优先级的任务先执行,这一点可以通过依赖(dency)来解决优先级的问题。
原则描述:优先级的判定是建立在依赖操作完成后对下一步操作的排序下。
14、NSBlockOperation用addExecutionBlock追加的操作是否为串行执行。如果不是、为什么要这么设计?
是串行异步执行。
15、主队列([NSOperationQueue mainQueue])可以不可以修改最大并发数?主队列下添加的操作、都会在主线程执行么?
a、一旦设置好之后不可以修改最大并发数!
b、不一定,如果是NSBlockOperation用addExecutionBlock方法就会在子线程中执行任务。
16、GCD的并行队列一定会开辟新的线程?
只有异步才会开启新线程,串并行与开启线程无关。
17、NSOperation的添加进队列后可不可以追加依赖?GCD任务组添加监听后可不可以追加任务?
a、NSOperation进入队列后不可以添加依赖,必须在添加队列之前设置。
b、GCD任务组具备追加任务的功能,前提是监听并未被触发。
18、dispatch_once如何实现一次性代码?
a、当onceToken == 0 的时候、才允许进入函数内部执行初始化的block。
b、block执行完将onceToken修改。
c、(稍微扩展一下啊)当多个线程同时调用dispatch_once方法的时候、内部会借用信号量来进行线程控制、进一步保证每个block代码只执行一次。
重写的相关代码:
```
#pragma mark - Override NSOperation
//下面都是重写的NSOperation的方法,自定义NSOperation需要这些操作
/**
* 开始这个NSOperation, @autoreleasepool {}为了自动释放我们的类。
*/
- (void)start {
@autoreleasepool {
[_locklock];
self.started=YES;//赋值开始标记为YES
if([selfisCancelled]) {
//如果这时候被取消了,调用取消方法
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
self.finished=YES;//标记结束位YES
//或者如果在准备开始,并且没有结束,并且没有运行中,执行以下操作
}elseif([selfisReady] && ![selfisFinished] && ![selfisExecuting]) {
//请求失败
if(!_request) {
self.finished=YES;//记录结束
if(_completion) {
//把错误信息传递给block
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
}else{
//设置正在执行为YES
self.executing=YES;
//调用开始方法
[self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
//如果模式为YYWebImageOptionAllowBackgroundTask并且在后台,后台下载
if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) {
__weak__typeof__(self) _self =self;
if(_taskID==UIBackgroundTaskInvalid) {
_taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{
__strong__typeof(_self)self= _self;
if(self) {
[selfcancel];
self.finished=YES;
}
}];
}
}
}
}
[_lockunlock];
}
}
//取消方法
- (void)cancel {
[_locklock];
//先检查是不是取消了,没有取消调用父类取消,设置自己取消为YES
if(![selfisCancelled]) {
[supercancel];
self.cancelled=YES;
//如果正在执行中,设置执行中为NO,调用取消
if([selfisExecuting]) {
self.executing=NO;
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
}
//如果已经开始,直接标记结束,不做其他处理
if(self.started) {
self.finished=YES;
}
}
[_lockunlock];
}
//手动实现KVO为了监听"isExecuting"值
- (void)setExecuting:(BOOL)executing {
[_locklock];
if(_executing!= executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing= executing;
[self didChangeValueForKey:@"isExecuting"];
}
[_lockunlock];
}
//以下这些get,set方法简单的赋值,
- (BOOL)isExecuting {
[_locklock];
BOOLexecuting =_executing;
[_lockunlock];
returnexecuting;
}
- (void)setFinished:(BOOL)finished {
[_locklock];
if(_finished!= finished) {
[self willChangeValueForKey:@"isFinished"];
_finished= finished;
[self didChangeValueForKey:@"isFinished"];
}
[_lockunlock];
}
- (BOOL)isFinished {
[_locklock];
BOOLfinished =_finished;
[_lockunlock];
returnfinished;
}
- (void)setCancelled:(BOOL)cancelled {
[_locklock];
if(_cancelled!= cancelled) {
[self willChangeValueForKey:@"isCancelled"];
_cancelled= cancelled;
[self didChangeValueForKey:@"isCancelled"];
}
[_lockunlock];
}
//cancel方法需要经常的去调用检测是否取消,因为不能够实时的检测
- (BOOL)isCancelled {
[_locklock];
BOOLcancelled =_cancelled;
[_lockunlock];
returncancelled;
}
//设置为并行
- (BOOL)isConcurrent {
return YES;
}
//设置为异步的
- (BOOL)isAsynchronous {
return YES;
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
if([keyisEqualToString:@"isExecuting"] ||
[keyisEqualToString:@"isFinished"] ||
[keyisEqualToString:@"isCancelled"]) {
returnNO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
//重写desc方法方便调试
- (NSString*)description {
NSMutableString *string = [NSMutableString stringWithFormat:@"<%@: %p ",self.class, self];
[stringappendFormat:@" executing:%@", [self isExecuting] ? @"YES" : @"NO"];
[stringappendFormat:@" finished:%@", [self isFinished] ? @"YES" : @"NO"];
[stringappendFormat:@" cancelled:%@", [self isCancelled] ? @"YES" : @"NO"];
[stringappendString:@">"];
returnstring;
}
```
相关知识点来源:
YYWebImage重写NSOperation的类为YYWebImageOperation
地址:https://github.com/ibireme/YYWebImage
NSRecursiveLock(递归锁)知识点:
http://www.cocoachina.com/ios/20150513/11808.html
参考作者AKyS佐毅:https://www.jianshu.com/p/aacaa1e370e4