zoukankan      html  css  js  c++  java
  • iOS多线程开发之NSOperation

    一、什么是NSOperation?

          NSOperation是苹果提供的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象、代码可读性更高、可控性更强,很屌的是加入了操作依赖。

          默认情况下,NSOperation单独使用时只能同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。讲到这里,我们不难发现GCD和NSOperation实现的方式很像,其实这更像是废话,NSOperation本身就是基于GCD的封装,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,前面《iOS多线程开发之GCD(上篇)》中已经阐述过GCD的实质:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。这样我们也可说NSOperation的本质就是:定义想执行的任务(NSOperation)并追加到适当的NSOperationQueue中。

    二、NSOperation使用

         1、创建任务

         NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。但它不能直接用来封装任务,只能通过它的子类来封装,一般的我们可以使用:NSBlockOperation、NSInvocationOperation或者定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。

       (1)NSBlockOperation

    复制代码
    - (void)invocationOperation{
    
        NSLog(@"start - %@",[NSThread currentThread]);
        
        // 创建NSInvocationOperation对象
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];
        
        // 调用start方法开始执行操作
        [op start];
        
        NSLog(@"end - %@",[NSThread currentThread]);
    }
    
    - (void)testRun{
        NSLog(@"invocationOperation -- %@", [NSThread currentThread]);
    }
    复制代码

        执行结果:

    2017-07-14 13:43:59.327 beck.wang[10248:1471363] start - <NSThread: 0x6100000614c0>{number = 1, name = main}
    2017-07-14 13:43:59.328 beck.wang[10248:1471363] invocationOperation -- <NSThread: 0x6100000614c0>{number = 1, name = main}
    2017-07-14 13:43:59.328 beck.wang[10248:1471363] end - <NSThread: 0x6100000614c0>{number = 1, name = main}

        分析:单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程同步执行操作,并没有开启新线程。

      (2)NSBlockOperation

    复制代码
    - (void)blockOperation{
        
        NSLog(@"start - %@",[NSThread currentThread]);
    
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"blockOperation--%@", [NSThread currentThread]);
        }];
        
        NSLog(@"end - %@",[NSThread currentThread]);
        
        [op start];
    }
    复制代码

        打印结果:

    2017-07-14 13:49:25.436 beck.wang[10304:1476355] start - <NSThread: 0x6100000653c0>{number = 1, name = main}
    2017-07-14 13:49:25.436 beck.wang[10304:1476355] end - <NSThread: 0x6100000653c0>{number = 1, name = main}
    2017-07-14 13:49:25.436 beck.wang[10304:1476355] blockOperation--<NSThread: 0x6100000653c0>{number = 1, name = main}

        分析:单独使用NSBlockOperation的情况下,NSBlockOperation也是在主线程执行操作,没有开启新线程。

        值得注意的是:NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行。

    复制代码
    - (void)blockOperation{
        
        NSLog(@"start - %@",[NSThread currentThread]);
    
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"blockOperation--%@", [NSThread currentThread]);
        }];
        
        // 添加额外任务(在子线程执行)
        [op addExecutionBlock:^{
            NSLog(@"addTask1---%@", [NSThread currentThread]);
        }];
        [op addExecutionBlock:^{
            NSLog(@"addTask2---%@", [NSThread currentThread]);
        }];
        [op addExecutionBlock:^{
            NSLog(@"addTask3---%@", [NSThread currentThread]);
        }];
        
        NSLog(@"end - %@",[NSThread currentThread]);
        
        [op start];
    }
    复制代码

         打印结果:

    复制代码
    2017-07-14 13:57:02.009 beck.wang[10351:1482603] start - <NSThread: 0x60000007cdc0>{number = 1, name = main}
    2017-07-14 13:57:02.009 beck.wang[10351:1482603] end - <NSThread: 0x60000007cdc0>{number = 1, name = main}
    2017-07-14 13:57:02.010 beck.wang[10351:1482603] blockOperation--<NSThread: 0x60000007cdc0>{number = 1, name = main}
    2017-07-14 13:57:02.010 beck.wang[10351:1482642] addTask1---<NSThread: 0x618000260e00>{number = 3, name = (null)}
    2017-07-14 13:57:02.010 beck.wang[10351:1482645] addTask3---<NSThread: 0x600000263200>{number = 5, name = (null)}
    2017-07-14 13:57:02.010 beck.wang[10351:1482643] addTask2---<NSThread: 0x610000264600>{number = 4, name = (null)}
    复制代码

        分析:blockOperationWithBlock任务在主线程中执行,addExecutionBlock的任务在新开线程中执行。

       

        (3)自定义NSOperation子类--重写main方法即可

        .h

    @interface ZTOperation : NSOperation
    
    @end

        .m

    复制代码
    @implementation ZTOperation
    
    - (void)main{
    
        // 在这里可以自定义任务
        NSLog(@"ZTOperation--%@",[NSThread currentThread]);
    }
    @end
    复制代码

        ViewController

    ZTOperation *zt = [[ZTOperation alloc] init];
    [zt start];

        打印结果:

    2017-07-14 14:05:58.824 beck.wang[10389:1490955] ZTOperation--<NSThread: 0x60000007a940>{number = 1, name = main}

        分析:任务在主线程中执行,不开启新线程。

        2、创建队列

        NSOperationQueue一共有两种队列:主队列、其他队列。其中其他队列同时包含了串行、并发功能,通过设置最大并发数maxConcurrentOperationCount来实现串行、并发!

      (1)主队列  -- 任务在主线程中执行

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

        (2)其他队列 -- 任务在子线程中执行

    NSOperationQueue *elseQueue = [[NSOperationQueue alloc] init];

        3、NSOperation  +  NSOperationQueue (任务追加到队列)

    复制代码
    // 添加单个操作:
     - (void)addOperation:(NSOperation *)op;
    
    // 添加多个操作:
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
    
    // 添加block操作:
    - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
    复制代码

          代码示例:

    复制代码
    - (void)addOperationToQueue
    {
        
        NSLog(@"start - %@",[NSThread currentThread]);
        
        // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 创建NSInvocationOperation
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];
        
        // 创建NSBlockOperation
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"task002 -- %@", [NSThread currentThread]);
        }];
        
        // 添加操作到队列中: addOperation:
        [queue addOperation:op1];
        [queue addOperation:op2];
        
        // 添加操作到队列中:addOperationWithBlock:
        [queue addOperationWithBlock:^{
            NSLog(@"task003-----%@", [NSThread currentThread]);
        }];
        
        NSLog(@"end - %@",[NSThread currentThread]);
    }
    
    - (void)testRun{
        NSLog(@"task001 -- %@", [NSThread currentThread]);
    }
    复制代码

         打印结果:

    2017-07-14 14:39:51.669 beck.wang[10536:1516641] start - <NSThread: 0x610000077640>{number = 1, name = main}
    2017-07-14 14:39:51.670 beck.wang[10536:1516641] end - <NSThread: 0x610000077640>{number = 1, name = main}
    2017-07-14 14:39:51.670 beck.wang[10536:1516686] task003-----<NSThread: 0x600000077200>{number = 3, name = (null)}
    2017-07-14 14:39:51.670 beck.wang[10536:1516689] task002 -- <NSThread: 0x61800007e080>{number = 5, name = (null)}
    2017-07-14 14:39:51.670 beck.wang[10536:1516687] task001 -- <NSThread: 0x61000007e1c0>{number = 4, name = (null)}

        分析:开启新线程,并发执行。

    三、NSOperationQueue管理

         1、队列的取消、暂停、恢复

             - (void)cancel;                           NSOperation提供的方法,可取消单个操作

             - (void)cancelAllOperations;         NSOperationQueue提供的方法,可以取消队列的所有操作

             - (void)setSuspended:(BOOL)b;    可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列

             - (BOOL)isSuspended;                判断暂停状态

             暂停或取消并不能使正在执行的操作立即暂停或取消,而是当前操作执行完后不再执行新的操作。两者的区别在于暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

         2、最大并发数 maxConcurrentOperationCount

              maxConcurrentOperationCount = - 1  表示不限制,默认并发执行;

              maxConcurrentOperationCount = 1 表示最大并发数为1,串行执行;

              maxConcurrentOperationCount > ([count] > =1)  表示并发执行,min[count,系统限制]。

             代码示例:

    复制代码
    - (void)operationQueue
    {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 设置最大并发操作数
        // queue.maxConcurrentOperationCount = - 1;  // 并发执行
        // queue.maxConcurrentOperationCount = 1; // 同步执行
         queue.maxConcurrentOperationCount = 2; // 并发执行
        
        [queue addOperationWithBlock:^{
            NSLog(@"task1-----%@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"task2-----%@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"task3-----%@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"task4-----%@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"task5-----%@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"task6-----%@", [NSThread currentThread]);
        }];
    }
    复制代码

         打印结果:

    复制代码
    // queue.maxConcurrentOperationCount = - 1
    
    2017-07-14 15:28:39.554 beck.wang[10772:1557342] task2-----<NSThread: 0x61800006d340>{number = 4, name = (null)}
    2017-07-14 15:28:39.554 beck.wang[10772:1557358] task3-----<NSThread: 0x6080000751c0>{number = 5, name = (null)}
    2017-07-14 15:28:39.554 beck.wang[10772:1557359] task4-----<NSThread: 0x610000071c00>{number = 6, name = (null)}
    2017-07-14 15:28:39.554 beck.wang[10772:1557339] task5-----<NSThread: 0x60000006ea40>{number = 7, name = (null)}
    2017-07-14 15:28:39.554 beck.wang[10772:1557340] task1-----<NSThread: 0x608000073500>{number = 3, name = (null)}
    2017-07-14 15:28:39.554 beck.wang[10772:1557360] task6-----<NSThread: 0x610000071c80>{number = 8, name = (null)}
    
    // 分析:线程数为6,并发执行
    
    -----------------------------------分割线----------------------------------------------
    
    // queue.maxConcurrentOperationCount =  1
    
    2017-07-14 15:27:04.365 beck.wang[10743:1555231] task1-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    2017-07-14 15:27:04.365 beck.wang[10743:1555231] task2-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    2017-07-14 15:27:04.365 beck.wang[10743:1555231] task3-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    2017-07-14 15:27:04.365 beck.wang[10743:1555231] task4-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    2017-07-14 15:27:04.366 beck.wang[10743:1555231] task5-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    2017-07-14 15:27:04.366 beck.wang[10743:1555231] task6-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
    
    // 分析:线程个数为1,同步执行
    
    -----------------------------------分割线----------------------------------------------
    
    // queue.maxConcurrentOperationCount =  2
    
    2017-07-14 15:18:26.162 beck.wang[10715:1548342] task2-----<NSThread: 0x608000079740>{number = 4, name = (null)}
    2017-07-14 15:18:26.162 beck.wang[10715:1548344] task1-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
    2017-07-14 15:18:26.162 beck.wang[10715:1548342] task4-----<NSThread: 0x608000079740>{number = 4, name = (null)}
    2017-07-14 15:18:26.162 beck.wang[10715:1548344] task3-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
    2017-07-14 15:18:26.162 beck.wang[10715:1548342] task5-----<NSThread: 0x608000079740>{number = 4, name = (null)}
    2017-07-14 15:18:26.163 beck.wang[10715:1548344] task6-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
    
    // 分析:线程个数为2,并发执行
    复制代码

         很明显,通过设置maxConcurrentOperationCount就能实现并发、串行功能是不是比GCD轻松多了!

         

         3、操作依赖

          NSOperation中我们可以为操作分解为若干个小的任务,通过添加他们之间的依赖关系进行操作,这个经常用到!这也是NSOperation吸引人的地方,不需要像GCD那样使用复杂的代码实现,addDependency就可以搞定!

    复制代码
    - (void)addDependency
    {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(2);
            NSLog(@"task1-----%@", [NSThread  currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"task2-----%@", [NSThread  currentThread]);
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"task3-----%@", [NSThread  currentThread]);
        }];
        
        // op2依赖于op1 执行顺序op1->op2 必须放在[添加操作队列]之前
        [op2 addDependency:op1];
        
        // 忌循环依赖 op2已经依赖于op1,切不可再让op1依赖于op2,形成循环依赖
        //[op1 addDependency:op2];
        
        // 添加操作队列
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    复制代码
         打印结果:
    2017-07-14 15:46:02.011 beck.wang[10854:1571574] task3-----<NSThread: 0x61800006d740>{number = 3, name = (null)}
    2017-07-14 15:46:04.085 beck.wang[10854:1571596] task1-----<NSThread: 0x60000006f040>{number = 4, name = (null)}
    2017-07-14 15:46:04.085 beck.wang[10854:1571574] task2-----<NSThread: 0x61800006d740>{number = 3, name = (null)}

        分析:task2一定在task1后面执行,因为执行task1前设置了线程等待2s,所有task3最早执行。

        4、操作优先级

    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8

       

        5、操作的监听

           可以监听一个操作是否执行完毕,如下载图片,需要在下载第一张图片后才能下载第二张图片,这里就可以设置监听。

    复制代码
    - (void)addListing{
    
        NSOperationQueue *queue=[[NSOperationQueue alloc]init];
        
        NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
            for (int i=0; i<3; i++) {
                NSLog(@"下载图片1-%@",[NSThread currentThread]);
            }
        }];
        
        // 监听操作的执行完毕
        operation.completionBlock=^{
            // 继续进行下载图片操作
            NSLog(@"--下载图片2--");
        };
       
        [queue addOperation:operation];
    }
    复制代码

         执行结果:

    2017-07-14 16:21:43.833 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
    2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
    2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
    2017-07-14 16:21:43.834 beck.wang[10930:1597955] --下载图片2--

        分析:下载图片1完成后才会执行下载图片2,这里类似知识点3中的添加依赖。

        留在最后的话:多线程不只是有GCD!如果你还没有用过NSOperation,还说什么呢?赶紧操练起来!当然他们各有各的使用场景,存在即合理!iOS多线程的三种技术GCD、NSThread、NSOperation就都介绍完了,需要了解 GCD、NSThread的可以回头看看我之前的博客。 

  • 相关阅读:
    python 装饰器
    python操作hbase
    python 数据压缩
    JsonSchema 启蒙
    python在webservice接口测试中的应用
    简单的python http接口自动化脚本
    解决python2安装MySQL-python模块报错
    实用小工具推荐(一)
    linux和mac使用virtualenv使用和安装
    少年,来点正能量吧!
  • 原文地址:https://www.cnblogs.com/includeao/p/7182907.html
Copyright © 2011-2022 走看看