美文网首页
OC--多线程NSOperation

OC--多线程NSOperation

作者: 啊哈呵 | 来源:发表于2017-09-02 16:12 被阅读20次

    前言

    NSOperation相比GCD的好处有:

    1、NSOperationQueue可以方便的调用cancel方法来取消某个操作。
    2、NSOperation可以方便的指定操作间的依赖关系。
    3、NSOperation可以通过KVO提供对NSOperation对象的精细控制。
    4、NSOperation可以方便的指定操作优先级。
    5、通过自定义NSOperation的子类可以实现操作重用。

    NSOperation

    直接上代码NSOperation.h

    @interface NSOperation : NSObject {
    
    - (void)start;//启动任务 默认加入到当前队列
    - (void)main; //自定义NSOperation,写一个子类,重写这个方法,在这个方法里面添加需要执行的操作。
    
    @property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
    - (void)cancel;//取消任务
    @property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
    @property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
    @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
    @property (readonly, getter=isReady) BOOL ready;//准备执行
    
    - (void)addDependency:(NSOperation *)op;//添加依赖
    - (void)removeDependency:(NSOperation *)op;//移除依赖
    //所有依赖关系,只读
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;
    //系统提供的优先级关系枚举
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    //执行优先级
    @property NSOperationQueuePriority queuePriority;
    //任务执行完成之后的回调
    @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
    //阻塞当前线程,等到某个operation执行完毕。
    - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
    //已废弃,用qualityOfService替代。
    @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、NSBlockOperation
    2、NSInvocationOperation
    3、NSOperation自定义子类

    NSBlockOperation
        NSLog(@"currentThread=====%@",[NSThread currentThread]);
        
        //1.封装操作
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            //当前线程中执行
            NSLog(@"任务1--%@",[NSThread currentThread]);
        }];
        
        // 2.1 追加操作,追加的操作在子线程中执行,必须在start前面
        // 2.2 但最大并发数为几个(这与dispatch_apply差不多)(包括主线程在内)
        [operation addExecutionBlock:^{
            NSLog(@"任务2--%@",[NSThread currentThread]);
        }];
        
        //3.启动执行操作
        [operation start];
    
        /*
         NSLog输出信息
         currentThread=====<NSThread: 0x60000026e400>{number = 3, name = (null)}
         任务1--<NSThread: 0x60000026e400>{number = 3, name = (null)}
         任务2--<NSThread: 0x608000266ec0>{number = 4, name = (null)}
         */
    
    NSInvocationOperation
        NSLog(@"currentThread=====%@",[NSThread currentThread]);
        //1.封装操作
        /*
         第一个参数:目标对象
         第二个参数:该操作要调用的方法,最多接受一个参数
         第三个参数:调用方法传递的参数,如果方法不接受参数,那么该值传nil
         */
        NSInvocationOperation *operation = [[NSInvocationOperation alloc]
                                            initWithTarget:self selector:@selector(run) object:nil];
        
        //2.启动操作
        [operation start];//开始调用run方法,在当前线程执行任务
        /*
         NSLog输出信息
         currentThread=====<NSThread: 0x6000000765c0>{number = 1, name = main}
         run=====<NSThread: 0x6000000765c0>{number = 1, name = main}
         */
    
    NSOperation自定义子类

    NSOperation有两个方法:main() 和 start()。
    1、如果想使用同步,那么最简单方法的就是把逻辑写在main()中,
    2、如果想使用异步,需要把逻辑写到start()中,然后加入到队列之中。

        //自定义的NSOperation,通过重写内部的main方法实现封装操作
        @implementation CustomOperation
        - (void)main {
           //2秒延时
            [NSThread sleepForTimeInterval:2];
            NSLog(@"CustomOperation main===%@",[NSThread   currentThread]);
        }
        //使用如下
        CustomOperation *operation = [CustomOperation new];
        [operation start];
        // 同步start,执行完operation的main,operation也dealloc了
    
    

    使用异步过程中还需要重载一些状态方法(可参考SDWebImageDownloaderOperation),如下

    @implementation CustomOperation
    
    @synthesize executing = _executing;
    @synthesize finished = _finished;
    
    - (void)start {
        // 就绪开始
        [self setExecuting:YES];
        // 2秒延时
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"=====%@",@"2秒后任务2完成");
            [self setExecuting:NO];
            [self setFinished:YES];
        });
    }
    
    #pragma mark - 需要重载的状态方法
    - (void)setFinished:(BOOL)finished {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
    
    - (void)setExecuting:(BOOL)executing {
        [self willChangeValueForKey:@"isExecuting"];
        _executing = executing;
        [self didChangeValueForKey:@"isExecuting"];
    }
    

    使用例子

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        // 任务1
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务1====%@",[NSThread currentThread]);
        }];
        
        CustomOperation *operation2 = [[CustomOperation new] init];
    
        // 设置任务1依赖任务2,等待任务2完成
        [operation1 addDependency:operation2];
        
        [queue addOperation:operation1];
        [queue addOperation:operation2];
        /*
         NSLog输出信息
         =====2秒后任务2完成
         任务1====<NSThread: 0x60800007f3c0>{number = 4, name = (null)}
         */
    
    各种状态
    @property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
    - (void)cancel;//取消任务
    @property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
    @property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
    @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
    @property (readonly, getter=isReady) BOOL ready;//准备执行
    
    458529-0d86e44aeae2c0d2.png

    线程start后并不是立即执行,而是进入一个就绪的状态(isReady),由系统调度执行。
    当这几个状态值改变时需要使用KVO通知,其中处于Pending、Ready跟Executing状态的operation是可以被cancel的,而当operation处于finished状态是无法被取消的。当operation成功结束、失败或者被取消了,isFinished的值都会被设置为yes,所以不能仅仅靠isFinished==YES认为operation成功执行。

    NSOperation任务执行完成之后的回调completionBlock
    operation.completionBlock = ^{
       NSLog(@"完成");
    };
    
    任务的执行顺序

    任务的执行顺序,以下情况都有影响:
    1、任务与任务依赖关系:

    [operationA addDependency:operationB]; 
    //可以在不同queue的NSOperation之间创建依赖关系
    //不要相互依赖,如A依赖B,B依赖A,死锁
    
    // 初始化三个块操作
    NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载 %@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"美化 %@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"更新 %@", [NSThread currentThread]);
    }];
    
    // 通过添加依赖可以控制线程执行顺序,依赖关系可以多重依赖
    // 注意:不要建立循环依赖,会造成死锁
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    
    // 直接加到队列里面会并发执行
    [self.queue addOperation:op3];
    [self.queue addOperation:op1];
    [self.queue addOperation:op2];
    

    2、任务的优先级

    typedef NS_ENUM(NSInteger, NSQualityOfService) {
    // 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
    NSQualityOfServiceUserInteractive = 0x21,
    // 在实现用户精确请求请求相关工作时使用UserInitiated QoS,但不要求精确到毫秒,比如动画。例如,如果用户打开email app马上查看邮件。
    NSQualityOfServiceUserInitiated = 0x19,
    // Utility QoS用于执行已经由用户请求自动发生的任务。例如,电子邮件应用程序可以被配置为每隔5分钟自动检查邮件。如果系统是非常有限的资源,而电子邮件检查被推迟几分钟这也是被允许的。
    NSQualityOfServiceUtility = 0x11,
    // Background QoS用于执行用户可能甚至都没有意识到正在发生的工作,比如email app可能使用它来执行索引搜索
    NSQualityOfServiceBackground = 0x09,
    // 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
    NSQualityOfServiceDefault = -1
    } NS_ENUM_AVAILABLE(10_10, 8_0);
    

    NSOperationQueue

    @interface NSOperationQueue : NSObject {
    - (void)addOperation:(NSOperation *)op;//添加任务
    - (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);//添加一个block形式的任务
    
    @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);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
    
    @property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0); // 底层对应的GCD队列
    
    - (void)cancelAllOperations;//取消队列中的所有任务
    
    - (void)waitUntilAllOperationsAreFinished;//阻塞当前线程,等到队列中的任务全部执行完毕。
    
    #if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
    @property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取当前队列
    @property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列
    #endif
    
    @end
    

    一般使用

        NSLog(@"test start------%@",[NSThread currentThread]);
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 任务1
        NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
        [queue addOperation:invocationOper];
        
        // 任务2
        NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务2------%@",[NSThread currentThread]);
        }];
        [queue addOperation:blockOper];
        
        // 任务3
        [queue addOperationWithBlock:^{
            NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
        }];
        
        
        NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务4------%@",[NSThread currentThread]);
        }];
        NSBlockOperation *block5 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务5------%@",[NSThread currentThread]);
        }];
        
        // 任务4、5
        [queue addOperations:@[block4,block5] waitUntilFinished:NO];
        NSLog(@"test end------%@",[NSThread currentThread]);
    
        /*
         NSLog输出信息
         test start------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
         test end------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
         QUEUEBlockOperationRun_<NSThread: 0x600000271800>{number = 4, name = (null)}
         任务1------<NSThread: 0x6000002718c0>{number = 5, name = (null)}
         任务2------<NSThread: 0x608000270880>{number = 6, name = (null)}
         任务4------<NSThread: 0x600000271ac0>{number = 7, name = (null)}
         任务5------<NSThread: 0x608000270a00>{number = 8, name = (null)}
         */
    
    队列类型

    1、主队列:[NSOperationQueue mainqueue];凡是放在主队列中的操作都在主线程中串行执行
    2、非主队列:[[NSOperationQueue alloc]init],并发和串行,默认是并发执行的

    最大并发数maxConcurrentOperationCount

    1、maxConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。
    2、maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。
    3、maxConcurrentOperationCount为0,不执行
    4、maxConcurrentOperationCount为1时,进行串行执行,但是线程可能是多条(如下代码测试)

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
        for (int i=0; i< 10; i++) {
            [queue addOperationWithBlock:^{
                NSLog(@"第%zd个-----%@",i,[NSThread currentThread]);
                [NSThread sleepForTimeInterval:0.1];
            }];
        }
        /*
         NSLOG输出信息:看出开了2个新线程,按顺序执行
         第0个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第1个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
         第2个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第3个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第4个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第5个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第6个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
         第7个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         第8个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
         第9个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
         */
    
    暂停和恢复以及取消
        //设置暂停和恢复
        //suspended设置为YES表示暂停,suspended设置为NO表示恢复
        //暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
        self.queue.suspended = !self.queue.suspended;
    
    
        //取消队列里面的所有操作
        //取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
        //取消操作是不可以恢复的
        [self.queue cancelAllOperations];
    

    cancelAllOperations取消队列里面的所有操作
    1、移除还没有开始执行的任务,Operation里面还没有执行不需要处理
    2、正在执行的任务,Operation的isCancelled为YES,系统不会让我们的Operation自动停止运行代码,但是我们可以监听isCancelled状态,处理自定义Operation里面的逻辑

    @implementation CustomOperation
    - (void)main {
        NSLog(@"CustomOperation start===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
        //2秒延时
        [NSThread sleepForTimeInterval:2];
        NSLog(@"CustomOperation end===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
    }
    
    
    - (void)test{
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        // 任务1
        [queue addOperation:[CustomOperation new]];
        // 任务2
        [queue addOperationWithBlock:^{
            
            [NSThread sleepForTimeInterval:1];
            [queue cancelAllOperations];
            NSLog(@"CancelAllOperations------%@",[NSThread currentThread]);
        }];
    
        /*
         NSLog输出信息
         CustomOperation start===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=0
         CancelAllOperations------<NSThread: 0x600000263780>{number = 5, name = (null)}
         CustomOperation end  ===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=1
         */
    }
    
    等待所有操作完成
    [queue waitUntilAllOperationsAreFinished];//阻塞当前线程,等到队列中的任务全部执行完毕。
    

    相关文章

      网友评论

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

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