iOS多线程--NSOperation

作者: 9d8c8692519b | 来源:发表于2017-08-31 17:40 被阅读103次

    1. NSOperation简介

    NSOperation是苹果提供给我们的一套自带线程管理的抽象类。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

    1)优点:自带线程周期管理,操作上可更注重自己逻辑。
    2)缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

    NSOperation需要配合NSOperationQueue来实现多线程。因为默认情况下,NSOperation单独使用时系统同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。

    因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。

    我们为什么使用NSOperation?

    在iOS开发中,为了提升用户体验,我们通常会将操作耗时的操作放在主线程之外的线程进行处理。对于正常的简单操作,我们更多的是选择代码更少的GCD,让我们专注于自己的业务逻辑开发。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。

    使用NSOperation实现多线程的三个核心步骤:

    1.创建任务:先将需要执行的操作封装到一个NSOperation对象中。
    2.创建队列:创建NSOperationQueue对象。
    3.将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。
    之后呢,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

    2.核心步骤的实现

    2.1 创建任务 --- 核心1

    在前面简介上我们提到过NSOperation是个抽象类,并不能封装任务。我们只有使用它的子类来封装任务。我们有三种方式来封装任务。

    1.使用子类NSInvocationOperation
    2.使用子类NSBlockOperation
    3.定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
    在不使用操作队列NSOperationQueue,而单独使用NSOperation的情况下。系统只会同步执行操作,下面我们学习一下这三种创建方式。

    2.1.1 使用子类NSInvocationOperation ----创建任务

    代码实现如下:

    //MARK: 使用子类- NSInvocationOperation
    - (void)useInvocationOperation {
        
        NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(runLog) object:nil];
        // 调用start方法开始执行操作
        [invocationOp start];
    }
    

    runLog方法是一个共用的打印方法,如下: 接下来的示例依然会用到它

    - (void)runLog {
        NSLog(@"1------%@", [NSThread currentThread]);
    }
    

    输出控制台信息:

    2017-08-31 16:10:39.847 Multi-thread_NSOperation[44199:2115590] 1------<NSThread: 0x60000007e980>{number = 1, name = main}

    由以上信息我们可以2.1.1中得到:
    单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程执行操作,并没有开启新线程。
    下边再来看看NSBlockOperation

    2.1.2 使用子类NSBlockOperation ----创建任务

    代码实现如下:

    //MARK: 使用子类- NSBlockOperation
    - (void)useBlockOperation {
        
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            // 在主线程
            [self runLog];
        }];
        [op start];
    }
    

    输出控制台信息:

    2017-08-31 16:24:06.021 Multi-thread_NSOperation[44263:2120051] 1------<NSThread: 0x61800006b6c0>{number = 1, name = main}

    由以上信息我们可以从2.1.2中得到:
    单独使用NSBlockOperation的情况下,NSBlockOperation也是在主线程执行操作,并没有开启新线程。
    但是,NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行,下面让我们一起来看下:

    使用addExecutionBlock方法
    //MARK: 使用 addExecutionBlock
    - (void)useAddExecutionBlock {
        
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            // 在主线程
            [self runLog];
        }];
        
        // 添加额外的任务
        // 注意:只要NSBlockOperation封装的操作数 >1,就会异步执行操作
        [op addExecutionBlock:^{
            NSLog(@"2------%@", [NSThread currentThread]);
        }];
        [op addExecutionBlock:^{
            NSLog(@"3------%@", [NSThread currentThread]);
        }];
        [op addExecutionBlock:^{
            NSLog(@"4------%@", [NSThread currentThread]);
        }];
        
        [op start];
    }
    

    输出控制台信息:

    2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121607] 1------<NSThread: 0x608000066500>{number = 1, name = main}
    2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121646] 4------<NSThread: 0x6180000673c0>{number = 4, name = (null)}
    2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121649] 2------<NSThread: 0x600000068ac0>{number = 3, name = (null)}
    2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121647] 3------<NSThread: 0x600000068b80>{number = 5, name = (null)}

    由上面的信息我们可以看出,blockOperationWithBlock:方法中的操作是在主线程中执行的,而addExecutionBlock:方法中的操作是在其他线程中执行的,并且只要NSBlockOperation封装的操作数(添加额外的任务) >1,就会异步执行操作。

    2.1.3 使用定义继承自NSOperation的子类 (MyTestOperation) ----创建任务

    先定义一个继承自NSOperation的子类,我们把它命名为MyTestOperation。重写main方法,代码内容如下:

    MyTestOperation.h

    #import <Foundation/Foundation.h>
    
    @interface MyTestOperation : NSOperation
    
    @end
    

    MyTestOperation.m

    /**
     * 需要执行的任务 重写main方法
     */
    - (void)main
    {
        for (int i = 0; i < 5; ++i) {
            NSLog(@"%d-----%@",i,[NSThread currentThread]);
        }
    }
    

    //MARK: 使用定义继承自NSOperation的子类 MyTestOperation
    -(void)myTestOperation{
    MyTestOperation *op = [[MyTestOperation alloc]init];
    [op start];
    }

    控制台输出:

    2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 0-----<NSThread: 0x608000074480>{number = 1, name = main}
    2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 1-----<NSThread: 0x608000074480>{number = 1, name = main}
    2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 2-----<NSThread: 0x608000074480>{number = 1, name = main}
    2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 3-----<NSThread: 0x608000074480>{number = 1, name = main}
    2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 4-----<NSThread: 0x608000074480>{number = 1, name = main}

    由以上信息我们可以从2.1.2中得到:
    单独使用自定义子类的情况下,是在主线程执行操作,并没有开启新线程。
    下边我们再看看操作队列NSOperationQueue的创建

    2.2 创建队列(NSOperationQueue)--- 核心2

    NSOperationQueue的作用

    1. NSOperation可以调用start方法来执行任务,但默认是同步执行
    2. 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。
      这里值得一提的是和GCD中的并发队列、串行队列略有不同的是:NSOperationQueue可以分为两种队列:
      1. 主队列(mainQueue);
      2. 其他队列: 其中其他队列同时包含了串行、并发功能。下边是主队列、其他队列的基本创建方法和特点。
    2.2.1 主队列

    凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

    // 主队列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    2.2.2 其他队列

    添加到👇这种队列中的任务(NSOperation),就会自动放到子线程中执行
    同时包含了:串行、并发功能

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    

    2.3 将任务(NSOperation)加入到队列(NSOperationQueue)中 --- 核心3

    上面提到过NSOperation需要配合NSOperationQueue来实现多线程。即理解为任务需要让队列来管理。
    那么我们需要将创建好的任务加入到队列中去。总共有两种方法:

    /* 两种方法
         -(void)addOperation:(NSOperation*)op;// 1
         -(void)addOperationWithBlock:(void(^)(void))block;// 2
         */
    

    这两种方法的
    区别是
    1.方法1需要先创建任务,再将创建好的任务加入到创建好的队列中去。
    2.方法2无需先创建任务,在block中添加任务,直接将任务block加入到队列中。

    相同点
    addOperationWithBlock: 和 NSOperationQueue 都能够开启新线程,进行并发执行。
    两种方式的代码实现如下:

    - (void)operationQueue{
        /* 两种方法
         -(void)addOperation:(NSOperation*)op;
         -(void)addOperationWithBlock:(void(^)(void))block;
         */
        
        //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        //2.创建操作
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            [self runLog];
        }];
    
        //3.添加操作到队列中
        [queue addOperation:op];
        
        
        // 方法二
        //添加操作到队列中
        [queue addOperationWithBlock:^{
            [self runLog];
        }];
    }
    

    3. NSOperation的高级操作

    高级操作中主要包含的是使用NSOperation过程中的一些特性,让我们能够更加了解它。

    3.1 NSOperation操作依赖

    NSOperation可以添加操作依赖:保证操作的执行顺序!

    #pragma mark - NSOperation高级操作 -- NSOperation操作依赖
    - (void)highLevelTest {
    
        NSInvocationOperation *inO = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runLog) object:nil];
        NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block1======%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block2======%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block3======%@", [NSThread currentThread]);
        }];
        
        /**
         四个操作都是耗时操作,并且要求按顺序执行,操作2是UI操作
         添加操作依赖的注意点
         1.一定要在将操作添加到操作队列中之前添加操作依赖
         2.不要添加循环依赖
         优点:对于不同操作队列中的操作,操作依赖依然有效
         提示:任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。使用Operation的目的就是为了让开发人员不再关心线程
         */
        
        // 1.一定要在将操作添加到操作队列中之前添加操作依赖
        [block2 addDependency:block1];
        [block3 addDependency:block2];
        [inO addDependency:block3];
        // 2.不要添加循环依赖 解开注释即循环依赖了
        //    [block1 addDependency:block3];
        
        [[[NSOperationQueue alloc] init] addOperation:block1];
        [[[NSOperationQueue alloc] init] addOperation:block2];
        [[[NSOperationQueue alloc] init] addOperation:block3];
        
        [[NSOperationQueue mainQueue] addOperation:inO];
    }
    

    通过上面的给任务(Operation)添加操作依赖,可以让我们的任务按我们设计预想的顺序执行。

    3.2 任务(NSOperation)暂停/恢复 取消 操作

    应用场景:提高用户体验第一,当用户操作时,取消一切跟用户当前操作无关的进程,提升流畅度
    如在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
    1.添加操作依赖
    2.管理操作:重点!是操作队列的方法
    3.暂停/恢复 取消 操作
    4.开启合适的线程数量!(最多不超过6条)

    完成此类问题用到的实现代码如下:

    #pragma mark - NSOperation高级操作2
    - (void)highLevelTest2 {
        /**
         NSOperation高级操作
         一般开发的时候,会将操作队列(queue)设置成一个全局的变量(属性)
         */
        
        NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"---------");
        }];
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        [queue addOperationWithBlock:^{
            [self runLog];
        }];
        
        [queue addOperation:block1];
        
        /*
         暂停和恢复的适用场合:如在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
         */
        
        // 1.暂停操作  开始滚动的时候
        [queue setSuspended:YES];
        
        // 2.恢复操作  滑动结束的时候
        [queue setSuspended:NO];
        
        // 3.取消所有操作  接收到内存警告
        [queue cancelAllOperations];
        
        // 3.1补充:取消单个操作调用该NSOperation的cancel方法
        [block1 cancel];
        
        // 4.设置线程最大并发数,开启合适的线程数量 实例化操作队列的时候
        [queue setMaxConcurrentOperationCount:6];
        
        /**
         遇到并发编程,什么时候选择 GCD, 什么时候选择NSOperation
         1.简单的开启线程/回到主线程,选择GCD:效率更高,简单
         2.需要管理操作(考虑到用户交互!)使用NSOperation
         */
    }
    

    3.3 NSOperation实现线程间通信

    NSOperation实现线程间通信:
    1)利用代理进行消息传递
    2)利用通知实现消息传递
    3)利用block进行消息传递

    你可能还要示例源码 在这里 源码
    您可能还对 iOS多线程--GCD 感兴趣
    您可能还对 iOS多线程--NSOperation 感兴趣
    您可能还对 iOS多线程--NSThread 感兴趣

    相关文章

      网友评论

        本文标题:iOS多线程--NSOperation

        本文链接:https://www.haomeiwen.com/subject/sewfjxtx.html