多线程之NSOperation的简单使用
来源:互联网 发布:网络管理需求分析 编辑:程序博客网 时间:2024/05/19 18:13
和多线程相关的基础知识,总共只有四个:pthread、NSThread、GCD和NSOperation。在前面,我们已经整理完了3个,现在就剩下NSOperation这一个了,下面,我们就继续学习这最后一个。
一、NSOpertion的基本使用
NSOperation是一个抽象类,它本身并不具备封装操作的能力,必须使用它的子类。这一点和我们之前学过的UIGestureRecognizer类似。NSOperation有两个很重要的子类:NSInvocationOperation和NSBlockOperation,它们都可以和NSOperationQueue配合来实现多线程编程,但是在实际用法上又不太一样。下面,我们就通过具体的代码示例来演示一下它们的使用和区别。
1、NSInvocationOperation
同为多线程技术,而且也都是基于OC,NSInvocationOperation和NSThread在使用上有很多相同的地方:1、都可以通过- initWithTarget: selector: object:创建线程对象并封装任务;2、创建完线程对象以后,都要调用- start方法来启动任务:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // invocationOperation [self invocationOperation];}// MARK:- NSInvocationOperation相关的代码- (void)invocationOperation { // 创建NSInvocationOperation对象 NSInvocationOperation *invp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master) object:nil]; /** * initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#> * 第一个参数 : 表示目标对象; * 第二个参数 : 方法选择器; * 第三个参数 : 可以通过这个参数给方法选择器中的方法传递参数,也可以为空。 */ // 开启任务 [invp start];}// MARK:- 封装的任务- (void)master { // 打印当前任务和所在的线程 NSLog(@"任务---%@", [NSThread currentThread]);}
先运行程序,看一下控制台打印情况:
注意看一下线程的number,它并没有开子线程,还是在主线程中去执行任务。我们在前面说过,NSInvocationOperation要实现多线程编程,必须和NSOperationQueue配合使用。如果不是配合NSOperationQueue来执行相应的操作,不管你创建多少个对象,封装多少个任务,它都不会开线程。
2、NSBlockOperation
接下来再看一下NSBlockOperation的简单使用。和NSInvocationOperation所不同的是,NSBlockOperation创建对象和封装任务的过程就相对简单了,它有一个带block代码块的类方法,我们可以在创建对象的同时将任务封装进去:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // blockOperation [self blockOperation];}// MARK:- NSBlockOperation- (void)blockOperation { // 创建NSBlockOperation对象,然后利用它类方法的block块封装任务 NSBlockOperation *blcp1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"任务1---%@", [NSThread currentThread]); }]; // 启动任务 [blcp1 start]; NSBlockOperation *blcp2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"任务2---%@", [NSThread currentThread]); }]; [blcp2 start]; NSBlockOperation *blcp3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"任务3---%@", [NSThread currentThread]); }]; [blcp3 start];}
与NSInvocationOperation的用法类似,在创建完对象以后,要调用- start方法才能启动封装的任务。运行程序,看一下控制台打印:
从打印的情况来看,它也没有开子线程。原因还是没有结合NSOperationQueue来使用。不过,与NSInvocationOperation稍微有所不同的是,NSBlockOperation还有一个- addExecutionBlock:方法,我们可以在这个对象方法中追加任务:
// MARK:- NSBlockOperation- (void)blockOperation { // 创建NSBlockOperation对象,然后利用它类方法的block块封装任务 NSBlockOperation *blcp1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"blcp1---%@", [NSThread currentThread]); }]; // 追加任务 [blcp1 addExecutionBlock:^{ // 追加任务1 NSLog(@"blcp1追加任务1---%@", [NSThread currentThread]); }]; // 启动任务 [blcp1 start]; NSBlockOperation *blcp2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"blcp2---%@", [NSThread currentThread]); }]; // 追加任务 [blcp2 addExecutionBlock:^{ // 追加任务2 NSLog(@"blcp2追加任务1---%@", [NSThread currentThread]); }]; [blcp2 start]; NSBlockOperation *blcp3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务 NSLog(@"blcp3---%@", [NSThread currentThread]); }]; // 追加任务 [blcp3 addExecutionBlock:^{ // 追加任务3 NSLog(@"blcp3追加任务1---%@", [NSThread currentThread]); }]; [blcp3 addExecutionBlock:^{ // 追加任务3 NSLog(@"blcp3追加任务2---%@", [NSThread currentThread]); }]; [blcp3 addExecutionBlock:^{ // 追加任务3 NSLog(@"blcp3追加任务3---%@", [NSThread currentThread]); }]; [blcp3 start];}
运行程序,仔细看一下控制台打印出来的信息,尤其要特别注意一下追加任务所在的线程:
从控制台打印出的信息来看,如果同一个操作对象中不止一个任务,那么它肯定会开子线程,并且是并发执行。但是,这并不意味着一定是子线程!比如说,blcp3并不是追加的任务,但是它却是在子线程中执行的。而通过操作对象blcp3追加的任务2反倒是在主线程执行。总之,如果一个操作对象中的任务量大于1,那么它一定会开子线程并发执行。至于哪个任务是在子线程中执行,哪个任务是在主线程中执行,并不确定。
二、NSOperationQueue
在上面的实例中,我们分别演示了NSInvocationOperation和NSBlockOperation在没有结合NSOperationQueue时的使用情况。下面,我们就结合NSOperationQueue来使用一下,看看会发生什么事情。
1、NSInvocationOperation和NSOperationQueue
在学习GCD基础知识的过程中,我们知道,队列可以分为并发队列和串行队列。获取这两种队列的具体操作如下:
并发队列:通过dispatch_queue_create( )函数传入DISPATCH_QUEUE_CONCURRENT参数,以及通过dispatch_get_global_queue( )函数可以得到一个并发队列;
串行队列:通过dispatch_queue_create( )函数传入DISPATCH_QUEUE_SERIAL参数,以及通过dispatch_get_main_queue( )函数可以得到一个串行队列。
NSOperationQueue里面的队列和GCD中的队列稍微有所不同,它主要分为主队列和非主队列。其中,通过[NSOperationQueue mainQueue]获取到的队列为主队列,也就是串行队列;而通过[[NSOperationQueue alloc] init]得到的队列为非主队列。并且,通过这种方式获取到的队列比较特殊,它同时具备并发和串行的特征。但是,它默认情况下是并发队列:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // NSInvocationOperation和NSOperationQueue组合使用 [self invocationOperationWithQueue];}// MARK:- NSInvocationOperation和NSOperationQueue组合使用- (void)invocationOperationWithQueue { // 创建NSInvocationOperation对象 NSInvocationOperation *invp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master) object:nil]; NSInvocationOperation *invp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(alphaGo) object:nil]; // 创建一个主队列 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; // 串行队列 // 创建非主队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 同时具备并发和串行的功能;默认情况下是并发队列 // 将操作对象添加到队列中 [mainQueue addOperation:invp]; // 将invp添加到主队列中 [queue addOperation:invp2]; // 将invp2添加到非主队列中}// MARK:- 封装的任务- (void)master { // 封装的任务 NSLog(@"master任务---%@", [NSThread currentThread]);}// MARK:- 再封装一个测试任务- (void)alphaGo { // 封装的任务 NSLog(@"alphaGo任务---%@", [NSThread currentThread]);}
在上面的代码中,我们创建了两种队列,并且分别封装了不同的任务。运行程序,注意看一下控制台打印出来线程的情况:
从上面的运行图中可以看出,添加到主队列中的任务依旧是在主线程中执行,而添加到非主队列中的任务默认是在子线程中执行。不过,这个还看不出到底是串行还是并发。接下来,我们就分别给它们封装多个任务进行测试。先来看主队列中添加多个任务的情况:
// MARK:- NSInvocationOperation和NSOperationQueue组合使用- (void)invocationOperationWithQueue { // 创建NSInvocationOperation对象 NSInvocationOperation *mq1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master1) object:nil]; NSInvocationOperation *mq2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master2) object:nil]; NSInvocationOperation *mq3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master3) object:nil]; // 创建一个主队列 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; // 串行队列 // 将操作对象添加到队列中 [mainQueue addOperation:mq1]; // 将mq1添加到主队列中 [mainQueue addOperation:mq2]; [mainQueue addOperation:mq3];}// MARK:- 封装的任务- (void)master1 { // 封装的任务 NSLog(@"master1任务---%@", [NSThread currentThread]);}// MARK:- 封装的任务- (void)master2 { // 封装的任务 NSLog(@"master2任务---%@", [NSThread currentThread]);}// MARK:- 封装的任务- (void)master3 { // 封装的任务 NSLog(@"master3任务---%@", [NSThread currentThread]);}
运行程序,看一下主队列中添加多个任务以后的执行情况:
从打印的信息可知,主队列是不开线程的,并且多个任务之间串行执行。再来看一下非主队列中多任务的执行情况:
// MARK:- NSInvocationOperation和NSOperationQueue组合使用- (void)invocationOperationWithQueue { // 创建NSInvocationOperation对象 NSInvocationOperation *mq1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master1) object:nil]; NSInvocationOperation *mq2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master2) object:nil]; NSInvocationOperation *mq3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(master3) object:nil]; // 创建非主队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 同时具备并发和串行的功能;默认情况下是并发队列 [queue addOperation:mq1]; // 将q1添加到非主队列中 [queue addOperation:mq2]; [queue addOperation:mq3];}// MARK:- 封装的任务- (void)master1 { // 封装的任务 NSLog(@"master1任务---%@", [NSThread currentThread]);}// MARK:- 封装的任务- (void)master2 { // 封装的任务 NSLog(@"master2任务---%@", [NSThread currentThread]);}// MARK:- 封装的任务- (void)master3 { // 封装的任务 NSLog(@"master3任务---%@", [NSThread currentThread]);}
运行程序,然后点击模拟器屏幕,注意看一下线程和任务的执行顺序:
从控制台打印出来的线程number和任务执行的顺序来看,非主队列会开子线程,并且多任务之间是并发执行的。
2、NSBlockOperation和NSOperationQueue
接下来,看一下NSInvocationOperation和NSOperationQueue结合使用的情况。因为主队列肯定是串行执行的,所以就没必要测试了。主要是看一下利用NSBlockOperation封装多个任务,然后再将其添加到非主队列中的执行情况:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // NSBlockOperation和NSOperationQueue组合使用 [self blockOperationWithQueue];}// MARK:- NSBlockOperation和NSOperationQueue组合使用- (void)blockOperationWithQueue { // 创建NSBlockOperation对象 NSBlockOperation *blck1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务1---%@", [NSThread currentThread]); }]; NSBlockOperation *blck2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务2---%@", [NSThread currentThread]); }]; NSBlockOperation *blck3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务3---%@", [NSThread currentThread]); }]; // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 将任务添加到队列中 [queue addOperation:blck1]; [queue addOperation:blck2]; [queue addOperation:blck3];}
运行程序,然后点击模拟器屏幕。注意看一下控制台打印出来的线程的number和多任务之间执行的顺序:
很显然,当使用NSBlockOperation封装多个任务,并且将它们添加到非主队列时,系统会开子线程,并且多任务之间是并发执行的。和前面一样,我们再追加几个任务看一下:
// MARK:- NSBlockOperation和NSOperationQueue组合使用- (void)blockOperationWithQueue { // 创建NSBlockOperation对象 NSBlockOperation *blck1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务1---%@", [NSThread currentThread]); }]; NSBlockOperation *blck2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务2---%@", [NSThread currentThread]); }]; NSBlockOperation *blck3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"封装任务3---%@", [NSThread currentThread]); }]; // 追加操作任务 [blck1 addExecutionBlock:^{ // 追加任务1 NSLog(@"blck1追加任务1---%@", [NSThread currentThread]); }]; [blck1 addExecutionBlock:^{ // 追加任务2 NSLog(@"blck1追加任务2---%@", [NSThread currentThread]); }]; [blck1 addExecutionBlock:^{ // 追加任务1 NSLog(@"blck1追加任务3---%@", [NSThread currentThread]); }]; [blck2 addExecutionBlock:^{ // 追加任务1 NSLog(@"blck2追加任务1---%@", [NSThread currentThread]); }]; [blck2 addExecutionBlock:^{ // 追加任务2 NSLog(@"blck2追加任务2---%@", [NSThread currentThread]); }]; [blck2 addExecutionBlock:^{ // 追加任务1 NSLog(@"blck2追加任务3---%@", [NSThread currentThread]); }]; // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 将任务添加到队列中 [queue addOperation:blck1]; [queue addOperation:blck2]; [queue addOperation:blck3];}
运行程序,看一下控制台打印的消息:
从最终打印的情况来看,不管是直接添加到非主队列中的任务,还是后来追加的任务,它们都是在子线程中并发执行的。
3、- addOperationWithBlock:方法
其实,除了像上面这种通过操作对象来封装任务、创建队列,然后再将任务添加到队列中的做法之外,还要特别介绍一种简便的方法。- addOperationWithBlock:方法可以让你在创建完队列之后,直接将任务封装起来添加到队列中:
// MARK:- NSBlockOperation和NSOperationQueue组合使用- (void)blockOperationWithQueue { // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ // 直接将任务封装起来添加到队列中 NSLog(@"封装任务1---%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ // 直接将任务封装起来添加到队列中 NSLog(@"封装任务2---%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ // 直接将任务封装起来添加到队列中 NSLog(@"封装任务3---%@", [NSThread currentThread]); }];}
- addOperationWithBlock:方法内部也有创建操作对象,然后再将任务添加到队列中去这些步骤。只不过,这些这些步骤现在不需要你去做,已经帮你做了,你只需要把任务封装进去就可以了。运行程序,看一下控制台打印出来的线程number和任务的执行顺序:
从图中可以看出,系统有开线程,并且多个任务之间是并发执行的。
三、自定义NSOperation
我们在之前的笔记中多次碰到过自定义类的情况,NSOperation也可以自定义,下面我们就简单的用一下。新建一个继承自NSOperation的ESOperation类,回到ViewController中包含它的头文件,然后实现如下代码:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 自定义NSOperation [self customOperation];}// MARK:- 自定义NSOperation- (void)customOperation { // 封装操作任务 ESOperation *op1 = [[ESOperation alloc] init]; ESOperation *op2 = [[ESOperation alloc] init]; // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 将任务添加到队列中 [queue addOperation:op1]; [queue addOperation:op2];}
现在运行程序不会有任何反应,因为我们没有封装任何任务。自定义NSOperation要封装任务,必须重写- main方法。来到ESOperation.m文件中,重写- main方法:
// MARK:- 重写- main方法,执行相应的任务- (void)main { // 将要封装任务的代码写在这里 NSLog(@"需要执行的任务---%@", [NSThread currentThread]);}
运行程序,然后点击模拟器屏幕看一下:
上面的代码看着非常简单,而且和前面系统自带的NSInvocationOperation类,以及NSBlockOperation类比起来,好像也没有更方便,或者是更强大。那么,为什么要自定义NSOperation呢?原因主要有以下几点:
1、在实际开发过程中,需要完成特定任务的代码量可能比较大,我们将这一部分代码单独抽出来,放在自定义类里面,可以有效减少控制器的代码量。说白了,就是专业的事情交给专门的类去做,让代码结构和功能更清晰合理;
2、特定的任务放在独立的类中,可以有效提高代码的复用率;
3、外界只需要知道某个类怎么使用就可以了,不必关心它内部的实现细节,从而有效提升开发效率。
NSOperation基础知识部分的笔记暂时先整理到这里,后面会接着整理NSOperation更高级的用法。详细代码参见ESNSOperationEXercise。
- 多线程之NSOperation的简单使用
- 网络多线程-NSOperation的简单使用
- NSOperation多线程的使用
- NSOperation的简单使用
- [第2章]多线程:NSOperation的简单使用
- iOS多线程NSOperation的使用
- 多线程:NSOperation 的基本使用
- 多线程:NSOperation 的依赖使用
- 多线程:NSOperation 的基本使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- IOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用 .
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- iOS多线程编程之NSOperation和NSOperationQueue的使用
- NSThread线程间的通信
- 多线程之GCD的简单使用
- GCD中常用的函数
- GCD知识进阶
- GCD在单例设计模式中的应用
- 多线程之NSOperation的简单使用
- NSOperation基础知识进阶
- 多线程技术的综合应用
- SDWebImage的基本使用
- SDWebImage框架重要的细节
- NSCache的基本使用
- RunLoop的基础知识
- 《Swift数据结构和算法》读书笔记专题
- 项目基本架构的搭建