美文网首页IOSiOS 知识点iOS面试资料
iOS基础深入补完计划--NSOperation

iOS基础深入补完计划--NSOperation

作者: kirito_song | 来源:发表于2018-03-20 14:57 被阅读687次

    (由于合在一起感觉一篇太长翻着累)

    iOS多线程目前总结了四篇

    欢迎移步O(∩_∩)O

    目录

    • NSOperation
    • 队列与操作
      • 队列NSOperationQueue
      • 操作NSOperation
    • 阻塞
    • NSOperation的API

    NSOperation

    NSOperation是苹果GCD、面向对象的封装。

    相比GCD的优点:
    队列与操作:

    既然是GCD的封装、自然逃不掉GCD的基本概念。操作与队列。

    • 队列NSOperationQueue

    三种向队列添加操作的方式

    - (void)addOperation:(NSOperation *)op;
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    - (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    
    队列插入操作后的执行顺序:

    首先、无论串行并行(并发数是否为1)的条件下。队列内操作的执行依赖两个要素。

    • 如果所插入的操作存在依赖关系、优先完成依赖操作。
    • 如果所插入的操作不存在依赖关系、队列并发数为1下采用先进先出的原则、反之直接开辟新的线程执行
      具体可以看下面的例子:
        //创建操作队列
        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        //创建最后一个操作
        NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
            sleep(1);
            NSLog(@"最后的任务");
        }];
        for (int i=0; i<5-1; ++i) {
            //创建多线程操作
            NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
                sleep(i);
                NSLog(@"第%d个任务",i);
            }];
            //设置依赖操作为最后一个操作
            [blockOperation addDependency:lastBlockOperation];
            [operationQueue addOperation:blockOperation];
            
        }
        //将最后一个操作加入线程队列
        [operationQueue addOperation:lastBlockOperation];
    

    有依赖的情况下输出:

    2018-03-16 15:08:21.666983+0800 test[4524:404134] 最后的任务
    2018-03-16 15:08:21.667267+0800 test[4524:404135] 第0个任务
    2018-03-16 15:08:22.667647+0800 test[4524:404141] 第1个任务
    2018-03-16 15:08:23.672276+0800 test[4524:404134] 第2个任务
    2018-03-16 15:08:24.669316+0800 test[4524:404132] 第3个任务
    

    相对的、我们可以取消依赖:注释掉[blockOperation addDependency:lastBlockOperation];

    2018-03-16 15:09:18.637169+0800 test[4551:406003] 第0个任务
    2018-03-16 15:09:19.641270+0800 test[4551:406002] 第1个任务
    2018-03-16 15:09:19.641270+0800 test[4551:406013] 最后的任务
    2018-03-16 15:09:20.640994+0800 test[4551:406006] 第2个任务
    2018-03-16 15:09:21.637335+0800 test[4551:406005] 第3个任务
    

    我们也可以看看多线程的提现、另1号操作依赖addDependency:lastBlockOperation。看看其他操作会不会直接执行。

    //设置依赖操作为最后一个操作
    NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
           sleep(i);
           NSLog(@"第%d个任务",i);
    }];
    //设置依赖操作为最后一个操作
    if (i == 1) {
         [blockOperation addDependency:lastBlockOperation];
    }
    [operationQueue addOperation:blockOperation];
    

    结果确实是除了1号操作、其他的都立即被分配的其他线程执行了。

    2018-03-16 15:20:16.679755+0800 test[4753:424790] 第0个任务
    2018-03-16 15:20:18.682874+0800 test[4753:424792] 第2个任务
    2018-03-16 15:20:18.682875+0800 test[4753:424795] 最后的任务
    2018-03-16 15:20:19.683923+0800 test[4753:424793] 第3个任务
    2018-03-16 15:20:19.683923+0800 test[4753:424790] 第1个任务
    
    需要注意的是:
    • 依赖必须在操作被添加到队列(确切来说应该是被执行)之前设置、否则无效。比如我们将一下两句调转:
    [operationQueue addOperation:blockOperation];
    [blockOperation addDependency:lastBlockOperation];
    

    输出的结果和注释掉依赖后相同:

    2018-03-16 15:10:48.814888+0800 test[4590:408731] 第0个任务
    2018-03-16 15:10:49.817847+0800 test[4590:408732] 第1个任务
    2018-03-16 15:10:49.817850+0800 test[4590:408731] 最后的任务
    2018-03-16 15:10:50.815294+0800 test[4590:408733] 第2个任务
    2018-03-16 15:10:51.815585+0800 test[4590:408730] 第3个任务
    
    • 依赖在添加进队列之后虽然不能追加。但是可以对某操作进行追加addExecutionBlock、也可以延后操作的执行。
    - (void)operationTest {
        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        
        NSBlockOperation * blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"进入操作1");
            sleep(3);
            NSLog(@"操作1完成");
        }];
        
        NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"进入依赖操作");
        }];
        
        [blockOperation2 addDependency:blockOperation1];
        
        [operationQueue addOperation:blockOperation1];
        [operationQueue addOperation:blockOperation2];
        
        [blockOperation1 addExecutionBlock:^{
            NSLog(@"进入追加操作");
            sleep(5);
            NSLog(@"追加操作完成");
        }];
    }
    

    打印结果:

    2018-03-20 13:00:55.635675+0800 test[2123:222591] 进入操作1
    2018-03-20 13:00:55.635675+0800 test[2123:225319] 进入追加操作
    2018-03-20 13:00:58.639154+0800 test[2123:222591] 操作1完成
    2018-03-20 13:01:00.641240+0800 test[2123:225319] 追加操作完成
    2018-03-20 13:01:00.641511+0800 test[2123:225319] 进入依赖操作
    
    
    • 操作的依赖关系与本身绑定、并不受限于同一个队列。即使所执行的队列不同、也可以完成依赖操作。
        NSOperationQueue *operationQueue1=[[NSOperationQueue alloc]init];
        NSOperationQueue *operationQueue2=[[NSOperationQueue alloc]init];
        //创建最后一个操作
        NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
            sleep(3);
            NSLog(@"最后的任务");
        }];
        NSBlockOperation *blockOperation0=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"第0个任务");
        }];
        NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"第1个任务");
        }];
        [blockOperation1 addDependency:lastBlockOperation];
        [operationQueue1 addOperation:blockOperation1];
        [operationQueue1 addOperation:blockOperation0];
        
        [operationQueue2 addOperation:lastBlockOperation];
    

    打印结果:

    2018-03-16 15:28:26.517760+0800 test[4874:439297] 第0个任务
    2018-03-16 15:28:29.520710+0800 test[4874:439298] 最后的任务
    2018-03-16 15:28:29.521107+0800 test[4874:439299] 第1个任务
    
    操作的追加

    我们可以通过一下方法将新的操作追加到NSBlockOperation对象中

    - (void)addExecutionBlock:(void (^)(void))block;
    

    但需要注意的是、追加的操作是并发执行的。具体的最大并发数、应该是由系统决定(因为我没找到哪个属性可以设置)。

        //主队列、必然是并发为1
        NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
        
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            sleep(1);
            NSLog(@"第%d个任务--%@",10,[NSThread currentThread]);
        }];
        
        
        for (int i=0; i<10; ++i) {
            [blockOperation addExecutionBlock:^{
                sleep(1);
                NSLog(@"第%d个任务--%@",i,[NSThread currentThread]);
            }];
        }
        
        [operationQueue addOperation:blockOperation];
    

    打印结果

    2018-03-16 16:21:38.739533+0800 test[5491:504081] 第0个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
    2018-03-16 16:21:38.739535+0800 test[5491:504020] 第10个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
    2018-03-16 16:21:38.739537+0800 test[5491:504083] 第1个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
    2018-03-16 16:21:38.739565+0800 test[5491:504080] 第2个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
    2018-03-16 16:21:39.739936+0800 test[5491:504083] 第5个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
    2018-03-16 16:21:39.739936+0800 test[5491:504020] 第4个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
    2018-03-16 16:21:39.739936+0800 test[5491:504080] 第6个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
    2018-03-16 16:21:39.739936+0800 test[5491:504081] 第3个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
    2018-03-16 16:21:40.741238+0800 test[5491:504020] 第7个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
    2018-03-16 16:21:40.741239+0800 test[5491:504083] 第8个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
    2018-03-16 16:21:40.741290+0800 test[5491:504080] 第9个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
    
    • 操作NSOperation

    两种个子类NSBlockOperationNSInvocationOperation、(当然、你也可以继承出一个NSOperation)。

    操作的优先级

    优先级只体现在两个时间点:

    • 依赖任务处理完成、队列对后续任务的调度。
    • 依赖队列从暂停转变为重新启动、后续任务的调度。

    简而言之就是。在队列同时需要调度执行的任务中、会按照优先级排序执行。

    如下所示(举个依赖完成的例子):

        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        
        operationQueue.maxConcurrentOperationCount = 1;
        
        NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"低优先级任务");
        }];
        blockOperation1.queuePriority = NSOperationQueuePriorityLow;
        
        NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"高优先级任务");
            sleep(1);
        }];
        blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
        
        NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
            
        }];
        
        [blockOperation1 addDependency:blockOperation3];
        [blockOperation2 addDependency:blockOperation3];
        
        [operationQueue addOperation:blockOperation1];
        [operationQueue addOperation:blockOperation2];
        [operationQueue addOperation:blockOperation3];
    

    打印结果:

    2018-03-16 18:33:52.435983+0800 test[6880:656581] 高优先级任务
    2018-03-16 18:33:53.437498+0800 test[6880:656579] 低优先级任务
    

    为了比较、我们可以把依赖去掉。这样执行顺序就会按照代码添加的顺序执行了。

        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        
        operationQueue.maxConcurrentOperationCount = 1;
        
        NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"低优先级任务");
        }];
        blockOperation1.queuePriority = NSOperationQueuePriorityLow;
        
        NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"高优先级任务");
            sleep(1);
        }];
        blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
    
        
        [operationQueue addOperation:blockOperation1];
        [operationQueue addOperation:blockOperation2];
    

    打印结果

    2018-03-16 18:30:40.293723+0800 test[6825:651558] 低优先级任务
    2018-03-16 18:30:40.294005+0800 test[6825:651555] 高优先级任务
    
    阻塞
    • 操作阻塞- (void)waitUntilFinished;

    阻塞当前线程、直到该操作执行完成。

        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
            
            sleep(3);
            NSLog(@"操作3执行完毕");
        }];
        
        NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"操作2开始执行");
            [blockOperation3 waitUntilFinished];
            NSLog(@"操作2执行完毕");
        }];
        [operationQueue addOperation:blockOperation2];
        [operationQueue addOperation:blockOperation3];
    

    打印结果

    2018-03-19 10:40:11.856272+0800 test[1611:101411] 操作2开始执行
    2018-03-19 10:40:14.857841+0800 test[1611:101413] 操作3执行完毕
    2018-03-19 10:40:14.858028+0800 test[1611:101411] 操作2执行完毕
    
    • 队列阻塞- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
      • 如果为YES。阻塞当前线程、直到队列该次添加的所有操作全部执行完成。
      • 如果为NO。就是批量添加操作而已。
        NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
        NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
            sleep(3);
            NSLog(@"操作3执行完毕");
        }];
        NSLog(@"添加操作");
        [operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
        NSLog(@"添加完成");
    

    打印结果:

    2018-03-19 10:43:58.998232+0800 test[1690:108032] 添加操作
    2018-03-19 10:44:02.000517+0800 test[1690:108155] 操作3执行完毕
    2018-03-19 10:44:02.001007+0800 test[1690:108032] 添加完成
    
    • 线程/队列死锁:

    这个不太好用语言描述。当阻塞生效时、若所依赖的操作无法并发完成。线程/队列将被锁死。举个例子:

        NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
        //pat1
        [operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
    
        //blockOperation3的执行相当于被添加到最后
        //pat2
        blockOperation3();
    

    最后的这个pat2将永远不会执行。因为主线程的并发为1、而这个1正在被占用。
    pat1等待pat2执行完毕。pat2又在等待着pat2执行完毕。造成死锁。
    如果operationQueue的并发数为2、pat2将会被放到另一个线程去执行、执行完毕解锁当前线程。就不会出现死锁的问题。

    NSOperation的API

    大概就一下这些东西、想看怎么用可以去开篇的帖子。或者自己搜搜。

    • 操作:

    可以KVC的状态(取消、进行、准备就绪、完成)/阻塞线程/completion回调/移除依赖等等....

    @interface NSOperation : NSObject {
    @private
        id _private;
        int32_t _private1;
    #if __LP64__
        int32_t _private1b;
    #endif
    }
    
    // 开始操作
    - (void)start;
    
    // 操作任务的入口,一般用于自定义NSOperation的子类 
    - (void)main;
    
    // 判断是否已经被取消
    @property (readonly, getter=isCancelled) BOOL cancelled;
    
    // 取消操作Operation,调用后不会自动马上取消,需要通过isCancelled方法检查是否被取消,然后自己编写代码退出当前的Operation
    - (void)cancel;
    
    // 是否正在执行
    @property (readonly, getter=isExecuting) BOOL executing;
    
    // 是否执行完
    @property (readonly, getter=isFinished) BOOL finished;
    
    // 判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同
    // 注意:此属性即将被弃用,之后使用asynchronous属性代替
    @property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
    
    // 是否异步执行
    @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
    
    // 是否准备好
    // 在start方法开始之前,需要确定Operation是否准备好,默认为YES,如果该operation没有准备好,则不会start。
    @property (readonly, getter=isReady) BOOL ready;
    
    // 添加依赖关系,如:[op1 addDependency:op2]; op2先执行,op1后执行  
    - (void)addDependency:(NSOperation *)op;
    
    // 取消依赖,注意:操作对象的依赖不能在操作队列执行时取消
    - (void)removeDependency:(NSOperation *)op;
    
    // 获取有依赖关系的Operation所组成的数组
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;
    
    // Operation优先级的枚举
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    
    // 优先级
    @property NSOperationQueuePriority queuePriority;
    
    // Operation完成后调用的代码块
    @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
    
    // 堵塞当前线程,直到该Operation执行结束,才会执行接下来的代码
    - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
    
    // 设定Operation的线程优先级,取值范围0~1,默认为0.5
    // 即使设定了线程优先级,也只能保证其在该线程的main()方法范围内有效,Operation的其他代码仍然执行在默认线程
    @property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
    
    // 用于系统自动合理的管理队列的资源分配。
    @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
    
    // 操作任务的名字
    @property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
    
    @end
    

    需要注意的是如果一个操作的状态为取消、进行、完成。是不可以被添加进队列的。

    • 对于队列:
    // 默认最大操作数为-1、也就是由系统分配
    static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
    
    @interface NSOperationQueue : NSObject {
    @private
        id _private;
        void *_reserved;
    }
    
    // 添加操作对象(NSOperation对象)
    - (void)addOperation:(NSOperation *)op;
    
    // 添加操作对象组(NSOperation对象),waitUntilFinished:是否阻塞当前线程,等待所有操作都完成
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
    
    // 添加操作任务
    - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
    
    // 获取操作任务对象组
    @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
    
    // 获取操作任务对象总数
    @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
    
    // 最大并发数
    @property NSInteger maxConcurrentOperationCount;
    
    // 是否暂停队列
    @property (getter=isSuspended) BOOL suspended;
    
    // 队列名字
    @property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
    
    // 用于系统自动合理的管理队列的资源分配
    @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
    
    // underlyingQueue属性的值是主线程的调度队列,此属性不能设置为其它值
    @property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
    
    // 取消所有操作任务
    - (void)cancelAllOperations;
    
    // 阻塞当前线程,等待所有操作执行完毕 
    - (void)waitUntilAllOperationsAreFinished;
    
    // 获取当前操作队列
    + (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
    
    // 获取主操作队列
    + (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
    
    @end
    

    相关文章

      网友评论

      • Delpan:operation执行前都可以添加依赖关系
        Delpan:@kirito_song 不客气
        kirito_song:嗯。�刚看了一眼、应该这么写才对。3Q

      本文标题:iOS基础深入补完计划--NSOperation

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