IOS高级开发之多线程(四)NSOperation
1.什么是NSOperation,NSOperationQueue?
NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlckOperation和NSInvocationOperation两个实体类。使用起来也非常简单,不过我们更多的使用是自己继承并定制自己的操作。
2.我们为什么使用NSOperation?
在IOS的开发中,为了照顾用户体验,我们通常将那些耗时的操作的放到主线程以为的线程去处理。对于一些简单的操作,我们可以使用代码更为简洁的GCD来进行处理。NSOperation也是基于GCD来实现,不过相对于GCD来说,可操控性更强,而且可以加入操作依赖。
1.可添加完成的代码块,在操作完成后执行。
2.添加操作之间的依赖关系,方便的控制执行顺序。
3.设定操作执行的优先级。
4.可以很方便的取消一个操作的执行。
5.使用KVO观察对操作状态进行的更改,isExcuting,isFinished,isCancelld.
NSOperation:执行操作的意思,就是需要在线程中执行的那段代码。
在GCD中,我们是放到block中去执行的,而在NSOperaion中,我们是使用NSOpration的子类NSInvocationOperation,NSBlokOperation,或者自定义子类来进行封装。
OperationQueue:操作队列,不同于GCD的先进先出,对于进入队列中的操作,首先进入准备就绪状态,不过这个取决于各个操作之间的依赖关系,然后进入就绪状态的操作的执行顺序,是由操作之间的相对优先级决定的。
操作队列通过设置最大并发数来控制并发,串行。
NSOperationQueue为我们提供了两种队列:主队列(运行在主线程),自定义队列(运行在后台)。
有关NSOperation、NSOperationQueue的使用:
NSOperation需要配合NSOperationQueue来实现多线程操作。默认情况下,单独使用NSOPeration,系统同步执行操作。配合NSOperationQueue来实现异步操作。
NSOperation的使用一般分为三个步骤:
1.创建操作:将需要执行的操作,封装到一个NSOperation中去。
2.创建队列:创建NSOperationQueue对象。
3.将操作加入到队列中去:将NSOperation对象加入到NSOperationQueue队列中去。
在之后,系统会自动将NSOpeationQueue中的NSOperation对象取出来,在新线程中执行。
我们来具体看下这三步是如何操作的:
先来看第一步,创建操作:
NSOperation是一个抽象类,所以我们要使用他的子类来进行封装操作。
有这么几个方式:
1.使用NSInvocationOperation
2.使用NSBlockOperation
3.自定义继承NSOpeation的子类,通过实现内部相应的方法来封装操作。
如果不配合NSOperationQueue单独使用NSOperation,系统同步执行操作。
我们来分别看下代码:
/* 使用子类NSInvocationOperation */ -(void)useInvocationOperation{ //1.创建NSInvocationOperation对象 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; //2.调用start方法执行操作 [op start]; } -(void)task1{ [NSThread sleepForTimeInterval:2.0]; NSLog(@"------%@",[NSThread currentThread]); }
打印:
2019-04-08 17:18:36.020622+0800 MyNSOperation[1118:14940] ------<NSThread: 0x600000fcf900>{number = 1, name = main}
可以看出,操作在当前线程中进行,并没有开启新线程。
但是,如果在其他线程中执行操作,打印则为其他线程:
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
打印:
2019-04-08 17:25:38.360812+0800 MyNSOperation[1379:19754] ------<NSThread: 0x6000003cd9c0>{number = 3, name = (null)}
接下来,再来看NSBlockOperation:
/** 使用子类NSBlockOperation */ -(void)useNSBlockOperation{ //1.创建NSBlockOperation对象 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"----%@",[NSThread currentThread]); } }]; //2.调用start方法执行操作 [op start]; }
打印结果:
2019-04-08 17:32:50.691864+0800 MyNSOperation[1669:25050] ----<NSThread: 0x600002f9f0c0>{number = 1, name = main} 2019-04-08 17:32:52.692550+0800 MyNSOperation[1669:25050] ----<NSThread: 0x600002f9f0c0>{number = 1, name = main}
可以看到NSBlock执行一个操作,是在当前线程中执行的,并没有开启一个新的线程。
不过也和NSInovationOperation一样,如果在其他线程中执行,则打印结果为其他线程。
不过NSBlockOperation还提供了一个方法:NSExecutionBlock:,通过这个方法就可以为NSExecutionBlock添加额外的操作。如果添加的操作多的话,blockOperationWithBlock:也可能会在其他线程中执行,这个是由系统决定的。来看下代码:
/** 使用NSBlockOperation 调用AddExecutionBlock; */ -(void)useBlockOperationAddExecutionBlock{ //1.创建NSBlockOperation对象 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"1-----%@",[NSThread currentThread]); } }]; //2.添加额外的操作 [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"2-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"3-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"4-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"5-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"6-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"7-----%@",[NSThread currentThread]); } }]; [op addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"8-----%@",[NSThread currentThread]); } }]; [op start]; }
打印结果:
2019-04-08 18:43:48.694212+0800 MyNSOperation[3981:56480] 1-----<NSThread: 0x6000003e6840>{number = 1, name = main} 2019-04-08 18:43:48.694212+0800 MyNSOperation[3981:56536] 3-----<NSThread: 0x600000382e00>{number = 5, name = (null)} 2019-04-08 18:43:48.694216+0800 MyNSOperation[3981:56538] 2-----<NSThread: 0x600000382d40>{number = 4, name = (null)} 2019-04-08 18:43:48.694235+0800 MyNSOperation[3981:56537] 4-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)} 2019-04-08 18:43:50.695591+0800 MyNSOperation[3981:56480] 1-----<NSThread: 0x6000003e6840>{number = 1, name = main} 2019-04-08 18:43:50.695591+0800 MyNSOperation[3981:56537] 4-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)} 2019-04-08 18:43:50.695592+0800 MyNSOperation[3981:56536] 3-----<NSThread: 0x600000382e00>{number = 5, name = (null)} 2019-04-08 18:43:50.695639+0800 MyNSOperation[3981:56538] 2-----<NSThread: 0x600000382d40>{number = 4, name = (null)} 2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56480] 7-----<NSThread: 0x6000003e6840>{number = 1, name = main} 2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56536] 6-----<NSThread: 0x600000382e00>{number = 5, name = (null)} 2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56537] 5-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)} 2019-04-08 18:43:52.697178+0800 MyNSOperation[3981:56538] 8-----<NSThread: 0x600000382d40>{number = 4, name = (null)} 2019-04-08 18:43:54.698766+0800 MyNSOperation[3981:56537] 5-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)} 2019-04-08 18:43:54.698777+0800 MyNSOperation[3981:56480] 7-----<NSThread: 0x6000003e6840>{number = 1, name = main} 2019-04-08 18:43:54.698777+0800 MyNSOperation[3981:56536] 6-----<NSThread: 0x600000382e00>{number = 5, name = (null)} 2019-04-08 18:43:54.698847+0800 MyNSOperation[3981:56538] 8-----<NSThread: 0x600000382d40>{number = 4, name = (null)}
可以看出,在添加了多个AddExcutionBlock操作之后,操作在不同的线程中异步执行了。这个是由系统决定的。
一般情况下,如果一个NSBlockOperation封装了多个操作,NSBlockOperation是否开启新线程,取决于操作的个数,如果添加的操作个数多,就会开启新线程。当然开启的新的线程数量也是由系统决定的。
不过我们经常遇到使用NSInvocationOperation以及NSBlockOperation无法满足的情况,这个时候我们就需要自定义NSOpration的子类啦!
可以通过重写main方法或者star方法来定义自己的NSOperaion对象。
我们先来看重写main方法:
新建一个类继承自NSOperation:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TestOperation : NSOperation @end NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TestOperation : NSOperation @end NS_ASSUME_NONNULL_END
在使用的地方:
//使用自定义的NSOperation -(void)useCustomOperaion{ //1.创建TestOperation对象 TestOperation *op = [[TestOperation alloc] init]; //2.调用start方法执行操作 [op start]; }
来看打印:
2019-04-08 19:04:03.931693+0800 MyNSOperation[4724:68006] 1-----<NSThread: 0x600001d113c0>{number = 1, name = main} 2019-04-08 19:04:05.933372+0800 MyNSOperation[4724:68006] 1-----<NSThread: 0x600001d113c0>{number = 1, name = main}
可以看出,在单独使用NSOperaion的时候,是在当前线程里的,接着我们看下使用NSOperaionQueue:
NSOperation主要包括两种队列:主队列和自定义队列。
凡是添加到主队列中的操作都会放到当前线程中执行。(不包括前面提到的addExcutionBlock添加的额外操作);
主队列的获取方法:
NSOperationQueue *[mainQueue = [NSOperationQueue mainQueue];
自定义队列:
添加到自定义队列中的操作就会放到子线程中去执行。同时包含了串行,并发的功能。
主队列是获取,而自定义队列则需要自己创建了:
//自定义队列的创建方法 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
有了队列,把操作加到队列中就OK了,我们来看下代码:
//把操作添加到队列中去: -(void)addOperationToQueue{ //1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //2.创建操作 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil]; //使用NSBlockOperation创建操作3 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"3--%@",[NSThread currentThread]); } }]; [op3 addExecutionBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"4----%@",[NSThread currentThread]); } }]; //3.将操作都添加到队列中去 [queue addOperation:op1];//[op1 start] [queue addOperation:op2]; [queue addOperation:op3]; }
来看打印:
2019-04-08 19:26:06.403243+0800 MyNSOperation[5573:82196] ------<NSThread: 0x6000028a0b80>{number = 3, name = (null)} 2019-04-08 19:26:06.403369+0800 MyNSOperation[5573:82195] 4----<NSThread: 0x6000028a7bc0>{number = 5, name = (null)} 2019-04-08 19:26:06.403372+0800 MyNSOperation[5573:82198] 3--<NSThread: 0x6000028a0bc0>{number = 4, name = (null)} 2019-04-08 19:26:08.406961+0800 MyNSOperation[5573:82198] 3--<NSThread: 0x6000028a0bc0>{number = 4, name = (null)} 2019-04-08 19:26:08.406952+0800 MyNSOperation[5573:82195] 4----<NSThread: 0x6000028a7bc0>{number = 5, name = (null)}
可以看到,加入到了不同的子线程中进行了异步执行。
再来看一种无需创建操作,直接写在block中的方法,使用NSOPerationWithBlock:
-(void)addOperationToQueueWithBlock{ //1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //2.使用addOperaionWithBlock添加操作到队列中 [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:1.0]; NSLog(@"1----%@",[NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:1.0]; NSLog(@"2----%@",[NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:1.0]; NSLog(@"3----%@",[NSThread currentThread]); }
打印结果:
2019-04-08 19:41:56.635903+0800 MyNSOperation[6125:93195] 4----<NSThread: 0x6000035d1fc0>{number = 5, name = (null)} 2019-04-08 19:41:56.635918+0800 MyNSOperation[6125:93186] ------<NSThread: 0x6000035eae00>{number = 3, name = (null)} 2019-04-08 19:41:56.635904+0800 MyNSOperation[6125:93188] 3--<NSThread: 0x6000035eae40>{number = 4, name = (null)} 2019-04-08 19:41:58.637956+0800 MyNSOperation[6125:93188] 3--<NSThread: 0x6000035eae40>{number = 4, name = (null)} 2019-04-08 19:41:58.637958+0800 MyNSOperation[6125:93195] 4----<NSThread: 0x6000035d1fc0>{number = 5, name = (null)}
再来看使用NSOperationQueue来控制串行,并行
这里注意一个重要属性,maxConcurrentOperationCount,它是一个队列中能同时并发执行的最大操作数量。
这个数默认情况为-1,表示不进行限制。
当为1的时候,很显然就是串行。
大于1的时候,就是并行。当然,你如果设置了很大,超过了系统 限制,系统就自动调整为他所允许的最大值。
//设置最大并发数量 -(void)setMaxConcurrentOperationCount{ //1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //2.设置最大的并发数量 queue.maxConcurrentOperationCount = 1;//串行队列 // queue.maxConcurrentOperationCount = 2;//并发队列 // queue.maxConcurrentOperationCount = 999; //3.添加操作 [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"1---%@",[NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"2---%@",[NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"3---%@",[NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i=0; i<2; ++i) { [NSThread sleepForTimeInterval:2.0]; NSLog(@"4---%@",[NSThread currentThread]); } }]; }
当设为1的时候的打印:
2019-04-08 19:52:04.936432+0800 MyNSOperation[6517:99856] 1---<NSThread: 0x6000003b5040>{number = 3, name = (null)} 2019-04-08 19:52:06.938095+0800 MyNSOperation[6517:99856] 1---<NSThread: 0x6000003b5040>{number = 3, name = (null)} 2019-04-08 19:52:08.939725+0800 MyNSOperation[6517:99855] 2---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)} 2019-04-08 19:52:10.940218+0800 MyNSOperation[6517:99855] 2---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)} 2019-04-08 19:52:12.941507+0800 MyNSOperation[6517:99856] 3---<NSThread: 0x6000003b5040>{number = 3, name = (null)} 2019-04-08 19:52:14.946991+0800 MyNSOperation[6517:99856] 3---<NSThread: 0x6000003b5040>{number = 3, name = (null)} 2019-04-08 19:52:16.951288+0800 MyNSOperation[6517:99855] 4---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)} 2019-04-08 19:52:18.956847+0800 MyNSOperation[6517:99855] 4---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)}
可以看出,为串行执行。
当设为2的时候的打印:
2019-04-08 19:53:48.113132+0800 MyNSOperation[6584:101034] 1---<NSThread: 0x60000357bc80>{number = 3, name = (null)} 2019-04-08 19:53:48.113146+0800 MyNSOperation[6584:101036] 2---<NSThread: 0x60000357bcc0>{number = 4, name = (null)} 2019-04-08 19:53:50.114523+0800 MyNSOperation[6584:101034] 1---<NSThread: 0x60000357bc80>{number = 3, name = (null)} 2019-04-08 19:53:50.114535+0800 MyNSOperation[6584:101036] 2---<NSThread: 0x60000357bcc0>{number = 4, name = (null)} 2019-04-08 19:53:52.116719+0800 MyNSOperation[6584:101035] 3---<NSThread: 0x600003542000>{number = 6, name = (null)} 2019-04-08 19:53:52.116730+0800 MyNSOperation[6584:101037] 4---<NSThread: 0x600003542880>{number = 5, name = (null)} 2019-04-08 19:53:54.117248+0800 MyNSOperation[6584:101037] 4---<NSThread: 0x600003542880>{number = 5, name = (null)} 2019-04-08 19:53:54.117292+0800 MyNSOperation[6584:101035] 3---<NSThread: 0x600003542000>{number = 6, name = (null)}
可以看出,是并发执行。并发的数量为2,但是具体开几个线程,这个就是由系统决定的。我们无需操心。
接下来,我们就要看NSOperation最令人着迷的地方:操作依赖,通过给操作添加依赖关系,我们就很容易控制操作之间的执行先后顺序。
NSOperation提供了3个接口供我们来管理和查看依赖关系:
1.addDependency添加依赖,使当前的操作依赖于参数的那个操作
2.removeDependency移除依赖
3.@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
我们来看一个添加依赖操作的代码把:
//添加依赖: -(void)addDependency{ //1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //2.创建操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程 } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程 } }]; //3.添加依赖 [op2 addDependency:op1]; //4.加到队列中去 [queue addOperation:op1]; [queue addOperation:op2]; }
来看打印:
2019-04-08 20:07:31.661222+0800 MyNSOperation[7082:109038] 1---<NSThread: 0x60000016bd40>{number = 3, name = (null)} 2019-04-08 20:07:33.662707+0800 MyNSOperation[7082:109038] 1---<NSThread: 0x60000016bd40>{number = 3, name = (null)} 2019-04-08 20:07:35.668239+0800 MyNSOperation[7082:109039] 2---<NSThread: 0x600000157900>{number = 4, name = (null)} 2019-04-08 20:07:37.673734+0800 MyNSOperation[7082:109039] 2---<NSThread: 0x600000157900>{number = 4, name = (null)}
可以看到,无论执行几次,2都会等1执行完毕,他才执行。
关于优先级与依赖关系:
优先级不能取代依赖关系。
优先级作用在同一队列的准备就绪状态下的依赖关系。
今天就先玩到这里把。