多线程之NSOperation和NSOperationQueue

时间:2022-05-21 15:07:07

  这篇文章里我将不过多的谈及理论知识,这些东西会的自然会,不会的,看多了也是云里雾里。下面我讲更多的用代码+注释的方式来讲如何使用NSOperation和NSOperationQueue。

  1、NSOperation。是抽象类,不能够直接使用,而是使用子类NSInvocationOperation和NSBlockOperation来实际执行任务。NSOperation本身和多线程是没有任何关系的,她只是封装了一个代码段和数据去实现一个功能。

  1.1、NSInvocationOperation,基于一个对象和selector来创建操作。看下面的代码:

 - (void)invocationOperation {

     NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOpration) object:nil];
[operation setCompletionBlock:^{
NSLog(@"执行完成,是否主线程:%@", [NSThread isMainThread] == ? @"YES" : @"NO");
}];
[operation start]; //启动任务
[operation cancel]; //取消任务
[operation isExecuting]; //任务是否在执行
[operation isFinished]; //任务是否已经结束
} - (void)startOpration {
NSLog(@"开始执行,是否主线程:%@", [NSThread isMainThread] == ? @"YES" : @"NO");
}

  首先是创建了一个operation,并给operation添加了一个需要执行的方法:startOpration。其中Line4中的方法将会在operation执行完成后执行,当然不是必须的,如果需要在operation执行完进行一些操作,可以写上这个方法。

  下面是执行的结果:

-- ::19.577 NSOperation[:] 开始执行,是否主线程:YES
-- ::19.577 NSOperation[:] 执行完成,是否主线程:NO

  根据上面执行的结果,我们可以发现, startOpration 这个方法是在主线程执行的。至于Line4~Line6是在子线程执行的,则不在今天的讨论内容中,略过。

  1.2、NSBlockOperation。相对于NSInvocationOperation,NSBlockOperation则是将selector中需要调用的方法使用Block进行了封装,使用起来更加的方便。关于Block的使用,不明白的同学可以参考我的上一篇博客。

  NSBlockOperation对象能够并发的执行一个或多个Block对象,所有相关的Block都执行完成之后,操作才算完成。下面看代码:

 - (void)blockOperation {
//创建一个操作
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d,%@,是否主线程:%@", __LINE__, [NSThread currentThread], [NSThread isMainThread] == ? @"YES" : @"NO");
}];
//通过addExecutionBlock方法添加Block操作
[blockOperation addExecutionBlock:^{
NSLog(@"%d,%@,是否主线程:%@", __LINE__, [NSThread currentThread], [NSThread isMainThread] == ? @"YES" : @"NO");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%d,%@,是否主线程:%@", __LINE__, [NSThread currentThread], [NSThread isMainThread] == ? @"YES" : @"NO");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%d,%@,是否主线程:%@", __LINE__, [NSThread currentThread], [NSThread isMainThread] == ? @"YES" : @"NO");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%d,%@,是否主线程:%@", __LINE__, [NSThread currentThread], [NSThread isMainThread] == ? @"YES" : @"NO");
}];
//执行操作
[blockOperation start];
//上面的几个Block是并发执行的,将会在不同的线程中执行,
}

  下面是执行的结果:

-- ::28.585 NSOperation[:] ,<NSThread: 0x7f97cad07c30>{number = , name = main},是否主线程:YES
-- ::28.585 NSOperation[:] ,<NSThread: 0x7f97cae223c0>{number = , name = (null)},是否主线程:NO
-- ::28.585 NSOperation[:] ,<NSThread: 0x7f97cac0eb60>{number = , name = (null)},是否主线程:NO
-- ::28.586 NSOperation[:] ,<NSThread: 0x7f97cad07c30>{number = , name = main},是否主线程:YES
-- ::28.585 NSOperation[:] ,<NSThread: 0x7f97cad06530>{number = , name = (null)},是否主线程:NO

  可以从上面的执行结果中,很容易的发现,程序是并行的执行。而创建的操作,也就是:Line4是在主线程中执行,而其他通过通过 addExecutionBlock 方法添加的Block操作则是在不同的线程中执行。

  通过上面的说明,我要总结的是:NSOperation以及子类,只是一个操作,本身无主线程和子线程的区分,可以在任意线程中使用。也就是,在主线程中创建的操作将在主线程中执行,在子线程中创建的操作将在子线程中执行。而实现多线程可以配合NSOperationQueue来实现。下面来介绍NSOperationQueue。

  2.NSOperationQueue,是操作队列,它用来管理一组NSOperation对象的执行,会根据需要自动为NSOperation对象开辟适合数量的线程,以完成任务的并行执行。下面上代码:

 - (void)operationQueue {
//创建一个任务队列,alloc(new)出来的任务队列将会在子线程中执行,并且是并发执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创建操作NSInvocationOperation
for (int i = ; i < ; i++) {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOpration) object:nil];
[queue addOperation:operation];
}
//创建NSBlockOperation操作
for (int i = ; i < ; i++) {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d,%@,%d", __LINE__, [NSThread currentThread], [NSThread isMainThread]);
}];
//将操作添加到队列中
[queue addOperation:blockOperation];
}
//设置最大并发数,当把最大并发数设置1时,此时队列相当于串行执行。
queue.maxConcurrentOperationCount = ;
//添加依赖关系
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是任务1,依赖于任务2");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是任务2");
}];
//添加依赖关系
//添加依赖关系时,注意不能添加互相依赖,如A依赖B,B依赖A
[op1 addDependency:op2];
//添加依赖关系必须在任务添加到队列之前设置
[queue addOperation:op1];
[queue addOperation:op2];
//回到主线程的操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%d,%@,%d", __LINE__, [NSThread currentThread], [NSThread isMainThread]);
}];
}

  下面是执行的结果:

 -- ::11.625 NSOperation[:] 开始执行,是否主线程:NO
-- ::11.625 NSOperation[:] 开始执行,是否主线程:NO
-- ::11.625 NSOperation[:] 开始执行,是否主线程:NO
-- ::11.625 NSOperation[:] 开始执行,是否主线程:NO
-- ::11.625 NSOperation[:] 开始执行,是否主线程:NO
-- ::11.626 NSOperation[:] ,<NSThread: 0x7f852bc099f0>{number = , name = (null)},
-- ::11.627 NSOperation[:] ,<NSThread: 0x7f852bf0db10>{number = , name = (null)},
-- ::11.628 NSOperation[:] ,<NSThread: 0x7f852bc46ee0>{number = , name = (null)},
-- ::11.628 NSOperation[:] ,<NSThread: 0x7f852bc4fe00>{number = , name = (null)},
-- ::11.628 NSOperation[:] ,<NSThread: 0x7f852be0a130>{number = , name = (null)},
-- ::11.629 NSOperation[:] 这是任务2
-- ::11.629 NSOperation[:] 这是任务1,依赖于任务2
-- ::11.631 NSOperation[:] ,<NSThread: 0x7f852bd05190>{number = , name = main},

  从上面的执行结果中,我们可以得到几个结论:1.首先队列中的任务都是在子线程中执行的;2.队列中的任务是并发执行的。而常用到的添加依赖关系,以及操作完成之后回到主线程的方法在上面的代码中都有体现。

  需要说明的是,一旦将operation添加到队列中后,队列就拥有了这个操作,操作将不能从队列中删除,只能取消一个操作,或者取消所有的操作。取消一个操作的方法:

[operation cancel];

  取消所有操作的方法:

[queue cancelAllOperations];

  如果想要暂停操作的执行可以使用方法: [queue setSuspended:YES]; 来实现。但是暂停不会导致正在执行的操作被暂停,只会阻止队列调度新的操作执行。可以通过方法: [queue setSuspended:NO]; 来继续队列的执行。

  3、最后做一个简单的总结。NSInvocationOperation 和 NSBlockOperation的功能是开启一个线程任务,这个线程任务处于子线程和主线程取决于线程任务是在哪个线程中开辟的。这些任务可以使用线程任务队列NSOperationQueue来管理,当new一个队列时,队列所管理的任务都在子线程中执行.当使用[NSOperationQueue mainQueue]时,队列管理的任务在主线程中执行。

  以上就是这篇文章的多有内容,如果有不对或者不足的地方,请大家在评论中指出,大家一起讨论。