多线程开发是平常开发任务中不可缺乏的一部分,在iOS开发中经常使用到的多线程开发技术有
GCD、NSOperation、NSThread
,本文主要讲解多线系列文章中关于NSOperation
的相关知识和使用详解。html
NSOperation
是苹果公司提供的一套完整的多线程解决方案,实际上它是基于GCD
更高一层的封装,彻底面向对象。相对于GCD而言使用更加的简单、代码更具可读性。包括网络请求、图片压缩在内的诸多多线程任务案例都很好的使用了NSOperation。固然NSOperation还须要NSOperationQueue
这一重要角色配合使用。数组
- 支持在操做对象之间依赖关系,方便控制执行顺序。
- 支持可选的完成块,它在操做的主要任务完成后执行。
- 支持使用KVO通知监视操做执行状态的变化。
- 支持设定操做的优先级,从而影响它们的相对执行顺序。
- 支持取消操做,容许您在操做执行时暂停操做。
和GCD同样NSOperation一样有任务的概念。所谓任务就是在线程中执行的那段代码,在GCD中是放在block执行的,而在NSOperation中是在其子类 NSInvocationOperation
、NSBlockOperation
、自定义子类
中执行的。和GCD不一样的是NSOperation须要NSOperationQueue的配合来实现多线程,NSOperation 单独使用时是同步执行操做,配合 NSOperationQueue 才能实现异步执行。安全
NSOperation中的队列是用NSOperationQueue
表示的,用过来存听任务的队列。bash
NSOperation是一个抽象类,为了作任何有用的工做,它必须被子类化。尽管这个类是抽象的,但它给了它的子类一个十分有用并且线程安全的方式来创建状态、优先级、依赖性和取消等的模型。NSOperation提供了三种方式来建立任务。 一、使用子类 NSInvocationOperation; 二、使用子类 NSBlockOperation; 三、自定义继承自 NSOperation 的子类,经过实现内部相应的方法来封装操做。网络
下面咱们先来看下NSOperation上面三种不一样方式的单独使用状况。多线程
NSInvocationOperation
类是NSOperation的一个具体子类,当运行时,它调用指定对象上指定的方法。使用此类可避免为应用程序中的每一个任务定义大量自定义操做对象。并发
-(void)invocationOperation{
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
[operation start];
}
-(void)operation{
for (int i = 0; i < 5; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}
复制代码
打印结果: 2020-03-19 17:09:46.189458+0800 ThreadDemo[44995:12677738] 0--<NSThread: 0x600000ba9e40>{number = 1, name = main} 2020-03-19 17:09:48.190629+0800 ThreadDemo[44995:12677738] 1--<NSThread: 0x600000ba9e40>{number = 1, name = main} 2020-03-19 17:09:50.191219+0800 ThreadDemo[44995:12677738] 2--<NSThread: 0x600000ba9e40>{number = 1, name = main} 2020-03-19 17:09:52.192556+0800 ThreadDemo[44995:12677738] 3--<NSThread: 0x600000ba9e40>{number = 1, name = main} 2020-03-19 17:09:54.193900+0800 ThreadDemo[44995:12677738] 4--<NSThread: 0x600000ba9e40>{number = 1, name = main}app
正如上面代码运行的结果显示,NSInvocationOperation单独使用时,并无开启新的线程,任务都是在当前线程中执行的。异步
NSBlockOperation
类是NSOperation的一个具体子类,它充当一个或多个块对象的包装。该类为已经使用操做队列且不但愿建立分派队列的应用程序提供了面向对象的包装器。您还可使用块操做来利用操做依赖、KVO通知和其余可能与调度队列不可用的特性。ide
-(void)blockOperationDemo{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}];
[operation start];
}
复制代码
打印结果: 2020-03-19 17:19:38.673513+0800 ThreadDemo[45160:12689966] 0--<NSThread: 0x600001081100>{number = 1, name = main} 2020-03-19 17:19:40.675074+0800 ThreadDemo[45160:12689966] 1--<NSThread: 0x600001081100>{number = 1, name = main} 2020-03-19 17:19:42.676649+0800 ThreadDemo[45160:12689966] 2--<NSThread: 0x600001081100>{number = 1, name = main} 2020-03-19 17:19:44.677073+0800 ThreadDemo[45160:12689966] 3--<NSThread: 0x600001081100>{number = 1, name = main} 2020-03-19 17:19:46.677379+0800 ThreadDemo[45160:12689966] 4--<NSThread: 0x600001081100>{number = 1, name = main}
如上面代码运行结果所示,NSBlockOperation单独使用时,并未开启新的线程,任务的执行都是在当前线程中执行的。
在NSBlockOperation类中还提供一个 addExecutionBlock
方法,这个方法能够添加一个代码执行块,当须要执行NSBlockOperation对象时,该对象将其全部块提交给默认优先级的并发调度队列。而后对象等待,直到全部的块完成执行。当最后一个块完成执行时,操做对象将本身标记为已完成。所以,咱们可使用块操做来跟踪一组执行的块,这与使用线程链接来合并来自多个线程的结果很是类似。不一样之处在于,因为块操做自己在单独的线程上运行,因此应用程序的其余线程能够在等待块操做完成的同时继续工做。须要说明的一点是,若是添加的任务较多的话,这些操做(包括 blockOperationWithBlock 中的操做)可能在不一样的线程中并发执行,这是由系统决定的。
- (void)blockOperationDemo {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"blockOperation--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock1--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock2--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock3--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock4--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock5--%@", [NSThread currentThread]);
}
}];
[operation start];
}
复制代码
打印结果: 2020-03-19 17:40:08.102543+0800 ThreadDemo[45536:12708941] executionBlock4--<NSThread: 0x600002a1ab00>{number = 1, name = main} 2020-03-19 17:40:08.102555+0800 ThreadDemo[45536:12709185] executionBlock2--<NSThread: 0x600002a57b80>{number = 8, name = (null)} 2020-03-19 17:40:08.102555+0800 ThreadDemo[45536:12709191] executionBlock5--<NSThread: 0x600002ab8980>{number = 9, name = (null)} 2020-03-19 17:40:08.102566+0800 ThreadDemo[45536:12709186] executionBlock3--<NSThread: 0x600002a7d440>{number = 4, name = (null)} 2020-03-19 17:40:08.102570+0800 ThreadDemo[45536:12709184] executionBlock1--<NSThread: 0x600002a3aa80>{number = 6, name = (null)} 2020-03-19 17:40:08.102576+0800 ThreadDemo[45536:12709187] blockOperation--<NSThread: 0x600002a7d600>{number = 5, name = (null)} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709187] blockOperation--<NSThread: 0x600002a7d600>{number = 5, name = (null)} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12708941] executionBlock4--<NSThread: 0x600002a1ab00>{number = 1, name = main} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709185] executionBlock2--<NSThread: 0x600002a57b80>{number = 8, name = (null)} 2020-03-19 17:40:10.103980+0800 ThreadDemo[45536:12709191] executionBlock5--<NSThread: 0x600002ab8980>{number = 9, name = (null)} 2020-03-19 17:40:10.103971+0800 ThreadDemo[45536:12709186] executionBlock3--<NSThread: 0x600002a7d440>{number = 4, name = (null)} 2020-03-19 17:40:10.103973+0800 ThreadDemo[45536:12709184] executionBlock1--<NSThread: 0x600002a3aa80>{number = 6, name = (null)}
正如上面代码运行结果所示,在调用了addExecutionBlock方法添加了组个多的任务后,开启新的线程,任务是并发执行的,blockOperationWithBlock中的任务执行也不是在当前的线程执行的。
若是使用子类 NSInvocationOperation、NSBlockOperation
不能知足平常需求,咱们还能够自定义子类。定一个类继承自NSOperation
,重写它的main
或者start
方法即可。
@interface CustomerOperation : NSOperation
@end
@implementation CustomerOperation
- (void)main{
if(!self.isCancelled){
for (int i = 0; i < 4; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}
}
-(void)customerOperation{
CustomerOperation *operation = [[CustomerOperation alloc]init];
[operation start];
}
复制代码
打印结果: 2020-03-19 20:28:54.473676+0800 ThreadDemo[47267:12811915] 0--<NSThread: 0x600001289040>{number = 1, name = main} 2020-03-19 20:28:56.474363+0800 ThreadDemo[47267:12811915] 1--<NSThread: 0x600001289040>{number = 1, name = main} 2020-03-19 20:28:58.474708+0800 ThreadDemo[47267:12811915] 2--<NSThread: 0x600001289040>{number = 1, name = main} 2020-03-19 20:29:00.476058+0800 ThreadDemo[47267:12811915] 3--<NSThread: 0x600001289040>{number = 1, name = main}
从上面代码运行结果显示能够看出,自定义的Operation并无开启新的线程,任务的执行是在当前的线程中执行的。
上面讲解了NSOperation单独使用的状况,下面咱们来看看NSOperationQueue队列配合NSOperation的使用状况。
在上面就已经说起过,NSOperation须要NSOperationQueue来配合使用实现多线程。那么咱们就须要将建立好的NSOperation对象加载到NSOperationQueue队列中。 NSOperationQueue提供了主队列和自定义队里两种队列,其中自定义队列中包含了串行和并发两种不一样的功能。
[NSOperationQueue mainQueue]
方式获取,凡是添加到主队列中的任务都会放到主线程中执行。[[NSOperationQueue alloc] init]
方式建立一个队列,凡是添加到自定义队列中的任务会自动放到子线程中执行。调用addOperation
方法将建立的operation对象添加到建立好的队列中。
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
复制代码
打印结果: 2020-03-19 21:01:45.868610+0800 ThreadDemo[47889:12843365] operation1--<NSThread: 0x6000012cd900>{number = 3, name = (null)} 2020-03-19 21:01:45.868610+0800 ThreadDemo[47889:12843364] operation2--<NSThread: 0x6000012e0640>{number = 6, name = (null)} 2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843365] operation1--<NSThread: 0x6000012cd900>{number = 3, name = (null)} 2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843364] operation2--<NSThread: 0x6000012e0640>{number = 6, name = (null)}
从上面代码运行的结果能够看出,开启了新的线程,任务是并发执行的。若是将queue换成是mainQueue,那么任务将会在主线程中同步执行。
若是任务不少时,一个个添加到队列难免有些麻烦,那么addOperations
就起做用了。
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
NSArray *operationList = @[operation1,operation2];
[queue addOperations:operationList waitUntilFinished:NO];
NSLog(@"end");
}
复制代码
打印结果: 2020-03-19 21:06:30.381594+0800 ThreadDemo[48047:12849411] end 2020-03-19 21:06:32.385653+0800 ThreadDemo[48047:12849496] operation1--<NSThread: 0x600001f4e880>{number = 8, name = (null)} 2020-03-19 21:06:32.385651+0800 ThreadDemo[48047:12849498] operation2--<NSThread: 0x600001fac740>{number = 4, name = (null)} 2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849496] operation1--<NSThread: 0x600001f4e880>{number = 8, name = (null)} 2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849498] operation2--<NSThread: 0x600001fac740>{number = 4, name = (null)}
从上面代码运行的记过能够看出,开启了新的线程,任务是并发执行的。若是将queue换成是mainQueue,那么任务将会在主线程中同步执行。
这里须要说明的一点的是
waitUntilFinished
参数,若是传YES,则表示会等待队列里面的任务执行完成后才会往下执行,也就是会阻塞线程。
NSOperationQueue还提供了一个addOperationWithBlock
方法能够将operation对象添加到NSOperationQueue中。
-(void)addOperationWithBlock{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
}
复制代码
打印结果: 2020-03-19 21:11:54.069593+0800 ThreadDemo[48192:12856146] operation1--<NSThread: 0x600000b0f740>{number = 4, name = (null)} 2020-03-19 21:11:54.069593+0800 ThreadDemo[48192:12856148] operation2--<NSThread: 0x600000b324c0>{number = 3, name = (null)} 2020-03-19 21:11:56.070432+0800 ThreadDemo[48192:12856148] operation2--<NSThread: 0x600000b324c0>{number = 3, name = (null)} 2020-03-19 21:11:56.070430+0800 ThreadDemo[48192:12856146] operation1--<NSThread: 0x600000b0f740>{number = 4, name = (null)}
从上面代码运行的记过能够看出,开启了新的线程,任务是并发执行的。若是将queue换成是mainQueue,那么任务将会在主线程中同步执行。
在前面的内容已经说起过,NSOperation单独使用时默认是系统同步执行的,若是须要并发执行任务,就须要NSOperationQueue的协同。那么决定是并发执行仍是同步执行的关键就在于最大并发任务数maxConcurrentOperationCount
。
maxConcurrentOperationCount
的值是-1,并不作限制,能够并发执行,如上面提到的NSBlockOperation添加多个任务块。maxConcurrentOperationCount
的值为1时,同步执行。maxConcurrentOperationCount
的值大于1时,并发执行。maxConcurrentOperationCount
的值并非表示并发执行的线程数量,而是在一个队列中可以同时执行的任务的数量。- (void)maxConcurrentOperationCount {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1;//串行队列
// queue.maxConcurrentOperationCount = 4;//并发队列
NSLog(@"maxCount=%ld", (long)queue.maxConcurrentOperationCount);
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation3--%@", [NSThread currentThread]);
}
}];
NSArray *operationList = @[operation1, operation2, operation3];
[queue addOperations:operationList waitUntilFinished:YES];
NSLog(@"end");
}
复制代码
打印结果: 2020-03-19 21:35:41.878534+0800 ThreadDemo[48619:12879620] maxCount=1 2020-03-19 21:35:43.882396+0800 ThreadDemo[48619:12879824] operation1--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:45.882889+0800 ThreadDemo[48619:12879824] operation1--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:47.886984+0800 ThreadDemo[48619:12879824] operation2--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:49.888093+0800 ThreadDemo[48619:12879824] operation2--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:51.893354+0800 ThreadDemo[48619:12879824] operation3--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:53.894355+0800 ThreadDemo[48619:12879824] operation3--<NSThread: 0x600000c7b240>{number = 3, name = (null)} 2020-03-19 21:35:53.894723+0800 ThreadDemo[48619:12879620] end
从上面的代码运行结果能够看出,开启了新的线程,任务是串行执行的。
若是将maxConcurrentOperationCount的值修改成2,那么打印的记过以下:
2020-03-19 21:36:59.126533+0800 ThreadDemo[48668:12881702] maxCount=2 2020-03-19 21:37:01.130238+0800 ThreadDemo[48668:12881793] operation1--<NSThread: 0x600003a92280>{number = 5, name = (null)} 2020-03-19 21:37:01.130246+0800 ThreadDemo[48668:12881794] operation2--<NSThread: 0x600003a45840>{number = 6, name = (null)} 2020-03-19 21:37:03.133480+0800 ThreadDemo[48668:12881793] operation1--<NSThread: 0x600003a92280>{number = 5, name = (null)} 2020-03-19 21:37:03.133489+0800 ThreadDemo[48668:12881794] operation2--<NSThread: 0x600003a45840>{number = 6, name = (null)} 2020-03-19 21:37:05.137502+0800 ThreadDemo[48668:12881794] operation3--<NSThread: 0x600003a45840>{number = 6, name = (null)} 2020-03-19 21:37:07.140419+0800 ThreadDemo[48668:12881794] operation3--<NSThread: 0x600003a45840>{number = 6, name = (null)} 2020-03-19 21:37:07.140713+0800 ThreadDemo[48668:12881702] end
从上面的运行结果能够看出,开启了新的线程,任务是并发执行的,并且每次执行的任务数最大为2个,那是由于咱们设置了maxConcurrentOperationCount的值为2,而添加了3个任务在队列中。
多线程操做可能永远也绕不过线程间通信这个话题。一般咱们将耗时的操做诸如网络请求、文件上传下载都放在子线程中执行,待执行完成以后须要回到主线程进行UI刷新操做,那么就会存在主线程和子线程之间的切换问题,好在NSOperation线程之间的通信是十分简单的。
-(void)threadCommunication{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 4; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"子线程--%@", [NSThread currentThread]);
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"主线程--%@", [NSThread currentThread]);
}
}];
}];
[queue addOperation:operation];
}
复制代码
打印结果: 2020-03-19 21:48:12.051256+0800 ThreadDemo[48922:12893188] 子线程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)} 2020-03-19 21:48:14.056107+0800 ThreadDemo[48922:12893188] 子线程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)} 2020-03-19 21:48:16.059279+0800 ThreadDemo[48922:12893188] 子线程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)} 2020-03-19 21:48:18.062773+0800 ThreadDemo[48922:12893188] 子线程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)} 2020-03-19 21:48:20.064401+0800 ThreadDemo[48922:12893108] 主线程--<NSThread: 0x600000bd2d00>{number = 1, name = main} 2020-03-19 21:48:22.065409+0800 ThreadDemo[48922:12893108] 主线程--<NSThread: 0x600000bd2d00>{number = 1, name = main}
NSOperation最大的亮点莫过于能够添加任务之间的依赖关系。所谓的依赖关系就是任务A须要等待任务B完成以后才能继续执行。NSOperation提供了三个方法为任务之间设置依赖关系。
-(void)addDependency:(NSOperation *)op;
添加依赖,使当前操做依赖于操做 op 的完成。-(void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操做对操做 op 的依赖。NSArray<NSOperation *> *dependencies;
在当前操做开始执行以前完成执行的全部操做对象数组。- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}
}];
// operation1依赖于operation2和operation3,则先执行operation2和operation3,而后执行operation1
[operation1 addDependency:operation2];
[operation1 addDependency:operation3];
NSArray *opList = @[operation1,operation2,operation3];
NSArray *dependencies = [operation1 dependencies];
NSLog(@"dependencies-%@",dependencies);
[queue addOperations:opList waitUntilFinished:YES];
NSLog(@"end");
}
复制代码
打印结果: 2020-03-19 22:11:32.567850+0800 ThreadDemo[49369:12918472] dependencies-( "<NSBlockOperation: 0x7ff341a06e40>", "<NSBlockOperation: 0x7ff341a06f50>" ) 2020-03-19 22:11:34.571689+0800 ThreadDemo[49369:12918726] 3---<NSThread: 0x6000037cf180>{number = 3, name = (null)} 2020-03-19 22:11:34.571694+0800 ThreadDemo[49369:12918732] 2---<NSThread: 0x6000037fbe40>{number = 7, name = (null)} 2020-03-19 22:11:36.577098+0800 ThreadDemo[49369:12918726] 3---<NSThread: 0x6000037cf180>{number = 3, name = (null)} 2020-03-19 22:11:36.577107+0800 ThreadDemo[49369:12918732] 2---<NSThread: 0x6000037fbe40>{number = 7, name = (null)} 2020-03-19 22:11:38.582249+0800 ThreadDemo[49369:12918726] 1---<NSThread: 0x6000037cf180>{number = 3, name = (null)} 2020-03-19 22:11:40.587676+0800 ThreadDemo[49369:12918726] 1---<NSThread: 0x6000037cf180>{number = 3, name = (null)} 2020-03-19 22:11:40.587996+0800 ThreadDemo[49369:12918472] end
从上面的代码运行结果能够看出operation2和operation3执行完成后才去执行的operation1。
NSOperation的另外一个亮点就是NSOperation提供了queuePriority
属性,该属性决定了任务在队列中执行的顺序。
queuePriority
属性只对同一个队列中的任务有效。queuePriority
属性不能取代依赖关系。NSOperationQueuePriorityNormal
,能够经过setQueuePriority:
方法来修改优先级。typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
复制代码
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}
}];
// operation1依赖于operation2和operation3,则先执行operation2和operation3,而后执行operation1
[operation1 addDependency:operation2];
[operation1 addDependency:operation3];
operation1.queuePriority = NSOperationQueuePriorityVeryHigh;
NSArray *opList = @[operation1,operation2,operation3];
NSArray *dependencies = [operation1 dependencies];
NSLog(@"dependencies-%@",dependencies);
[queue addOperations:opList waitUntilFinished:YES];
NSLog(@"end");
}
复制代码
打印结果: 2020-03-19 22:31:15.086135+0800 ThreadDemo[49743:12937692] dependencies-( "<NSBlockOperation: 0x7ffa6140a980>", "<NSBlockOperation: 0x7ffa6140a760>" ) 2020-03-19 22:31:17.087052+0800 ThreadDemo[49743:12937910] 3---<NSThread: 0x6000033d1f80>{number = 5, name = (null)} 2020-03-19 22:31:17.087060+0800 ThreadDemo[49743:12937909] 2---<NSThread: 0x6000033d1780>{number = 4, name = (null)} 2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937909] 2---<NSThread: 0x6000033d1780>{number = 4, name = (null)} 2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937910] 3---<NSThread: 0x6000033d1f80>{number = 5, name = (null)} 2020-03-19 22:31:21.090223+0800 ThreadDemo[49743:12937910] 1---<NSThread: 0x6000033d1f80>{number = 5, name = (null)} 2020-03-19 22:31:23.092879+0800 ThreadDemo[49743:12937910] 1---<NSThread: 0x6000033d1f80>{number = 5, name = (null)} 2020-03-19 22:31:23.093183+0800 ThreadDemo[49743:12937692] end
如上代码运行结果所示,即便将operation1的优先级设置为最高NSOperationQueuePriorityVeryHigh,operation1依然是最后执行的,那是由于operation1依赖于operation2和operation3,在operation2和operation3未执行完成以前,operation1一直是处于为就绪状态,即便优先级最高,也不会执行。
NSOperation包含了一个十分优雅的状态机来描述每个操做的执行。isReady → isExecuting → isFinished
。为了替代不那么清晰的state
属性,状态直接由上面那些keypath
的KVO
通知决定,也就是说,当一个操做在准备好被执行的时候,它发送了一个KVO
通知给isReady
的keypath
,让这个keypath
对应的属性isReady
在被访问的时候返回YES
。 每个属性对于其余的属性必须是互相独立不一样的,也就是同时只可能有一个属性返回YES,从而才能维护一个连续的状态:
isReady
: 返回YES
表示操做已经准备好被执行, 若是返回NO
则说明还有其余没有先前的相关步骤没有完成。isExecuting
: 返回YES
表示操做正在执行,反之则没在执行。isFinished
: 返回YES
表示操做执行成功或者被取消了,NSOperationQueue
只有当它管理的全部操做的isFinished
属性全标为YES
之后操做才中止出列,也就是队列中止运行,因此正确实现这个方法对于避免死锁很关键。- (void)cancel;
可取消操做,实质是标记 isCancelled 状态。 判断操做状态方法- (BOOL)isFinished;
判断操做是否已经结束。- (BOOL)isCancelled;
判断操做是否已经标记为取消。- (BOOL)isExecuting;
判断操做是否正在在运行。- (BOOL)isReady;
判断操做是否处于准备就绪状态,这个值和操做的依赖关系相关。- (void)waitUntilFinished;
阻塞当前线程,直到该操做结束。可用于线程执行顺序的同步。- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 会在当前操做执行完毕时执行 completionBlock。- (void)cancelAllOperations;
能够取消队列的全部操做。- (BOOL)isSuspended;
判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。10.- (void)setSuspended:(BOOL)b;
可设置操做的暂停和恢复,YES 表明暂停队列,NO 表明恢复队列。- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操做所有执行完毕。- (NSUInteger)operationCount;
当前队列中的操做数。 获取队列+ (id)currentQueue;
获取当前队列,若是当前线程不是在 NSOperationQueue 上运行则返回 nil。和其余多线程方案同样,解决NSOperation多线程安全问题,能够给线程加锁,在一个线程执行该操做的时候,不容许其余线程进行操做。iOS 实现线程加锁有不少种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock
等等各类方式。