美文网首页iOS面试
iOS 多线程NSOperation

iOS 多线程NSOperation

作者: YourSummer | 来源:发表于2022-05-24 17:08 被阅读0次

    一. 定义

    NSOperation是苹果公司提供的一套多线程解决方案, 它是基于GCD 的更抽象的"面向对象"封装.

    二. 对比GCD

    • 支持任务之间添加依赖关系, 控制执行顺序
    • 提供可选实现的任务完成block
    • 可使用KVO监听任务执行状态变化
    • 支持设置任务的优先级, 可控制任务的相对执行顺序
    • 提供取消操作

    三. 任务

    任务就是在线程中执行的代码, 在GCD中表现形式是block, 在NSOperationshi是在其子类NSInvocationOperation, NSBlockOperation, 自定义子类中执行任务代码.

    四. 队列

    NSOperationQueue表示队列, 用来存放任务的队列

    • NSOperation单独使用时是同步操作, 配合NSOperationQueue才能实现异步操作
    • GCD添加到队列中的任务遵守FIFO原则, 对于添加到NSOperationQueue中的任务, 首先根据任务之间的依赖关系决定任务的就绪状态, 进入就绪状态的任务由任务之间的相对优先级决定执行顺序
    • NSOperationQueue可以设置最大并发任务数量
    • NSOperationQueue 提供两种队列: 主队列和自定义队列,
      • 主队列在主线程运行
      • 自定义队列在后台执行

    五. 基本使用

    NSOperation是一个抽象类, 使用的时候必须子类化, 提供三种方式创建任务:

    1. 使用子类NSInvocationOperation
    NSInvocationOperation *invocationOpeartion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOpeartion) object:nil];
        
    // 如果没有添加到 NSOperationQueue, 则需要手动调用 start
    [invocationOpeartion start];
    
    /// 回调方法
    - (void)invocationOpeartion {
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }
    

    注意: NSInvocationOperation单独使用时, 并没有开启新的线程, 任务都是在当前线程中执行

    2. 使用子类NSBlockOperation
    • 2.1 简单使用
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }];
        
    [blockOperation start];
    

    注意: NSBlockOperation单独使用时, 并没有开启新的线程, 任务都是在当前线程中执行

    • 2.2: NSBlockOperation类有一个实例方法addExecutionBlock, 这个方法可以添加一个代码块, 当执行NSBlockOperation对象 start 时, 该对象将其所有块提交给默认优先级的并发调度队列. 然后对象等待所有的块完成执行. 当最后一个块完成执行时, 操作对象将自己标记为已完成. 由于块操作本身在单独的线程上运行, 所以应用程序的其他线程可以在等待块操作完成的同时继续工作.
      需要说明的一点是, 如果添加的任务较多的话, 这些操作(包括blockOperationWithBlock中的操作)可能在不同的线程中并发执行, 这是由系统决定的
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }];
        
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock1:%@", NSThread.currentThread);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock2:%@", NSThread.currentThread);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecutionBlock3:%@", NSThread.currentThread);
    }];
    [blockOperation start];
    

    在调用了addExecutionBlock方法添加了多组任务后,开启新的线程,任务是并发执行的,blockOperationWithBlock中的任务执行是否在当前的线程执行, 统一由系统调度.

    3. 自定义继承自NSOperation的子类
    @interface Operation : NSOperation
    
    @end
    
    @implementation Operation
    
    -(void)main {
        if (!self.isCancelled) {
            for (int i = 0; i < 4; i++) {
                sleep(2);
                NSLog(@"%d==%@", i, NSThread.currentThread);
            }
        }
    }
    
    @end
    
    // 使用:
    Operation *op = [[Operation alloc] init];
    [op start];
    

    注意: 自定义的Operation并没有开启新的线程,任务的执行是在当前的线程中执行的。

    六. 队列: NSOperationQueue

    NSOperationQueue提供了主队列和自定义队列

    • 主队列: [NSOperationQueue mainQueue];, 凡是添加到主队列的NSOperation任务都会放到主线程执行
    • 自定义队列: [[NSOperationQueue alloc] init], 凡是添加到自定义队列的NSOperation任务都会放到子线程执行.
    1.添加任务到队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 方式1: 
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }];
    [queue addOperation:blockOperation];
    
    // 方式2: 
    [queue addOperationWithBlock:^{
        // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }];
    
    
    

    // 注意: 不需要调用NSBlockOperationstart方法

    2.添加多个操作到队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 操作1
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }];
    
    // 操作2
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    
    // 方式1:
    [queue addOperation:blockOperation];
    [queue addOperation:invocationOperation];
    
    // 方式2:
    // waitUntilFinished参数,如果传YES,则表示会等待队列里面的任务执行完成后才会往下执行,也就是会阻塞线程
    [queue addOperations:@[blockOperation, invocationOperation] waitUntilFinished:true];
    
    /// 回调方法
    - (void)invocationOperation {
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
    }
    

    总结: waitUntilFinished参数,如果传YES,则表示会等待队列里面的任务执行完成后才会往下执行,也就是会阻塞线程

    七. 同步&并发

    NSOperationQueue有个一属性maxConcurrentOperationCount
    maxConcurrentOperationCount 默认-1, 不做限制, 在子线程中执行任务
    maxConcurrentOperationCount = 1, 队列中的任务会同步执行, 阻塞当前线程
    maxConcurrentOperationCount > 1, 队列中的任务会并发执行, 开启子线程执行
    maxConcurrentOperationCount值并不是表示并发执行的线程数量,而是在一个队列中能够同时执行的任务的数量。

    八. NSOperation线程间的通讯, 子线程完成任务, 会主线程更新UI

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        for (int i = 0; i < 5; i++) {
            sleep(2);
            NSLog(@"%d -- %@", i, NSThread.currentThread);
        }
        // 回主队列更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新UI");
        }];
    }];
    [queue addOperation:blockOperation];
    

    九. 任务的依赖关系

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        NSLog(@"1");
        
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2");
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3");
    }];
    
    // 1依赖于2和3, 意思是在2和3都执行完后才会执行1;
    [operation1 addDependency:operation2];
    [operation1 addDependency:operation3];
    // 将1的优先级设置最高
    operation1.queuePriority = NSOperationQueuePriorityHigh;
    
    [queue addOperations:@[operation1, operation2, operation3] waitUntilFinished:NO];
    //log: 2 3 1
    
    

    总结:
    queuePriority不能取代依赖关系
    queuePriority属性只对同一个队列有效

    十.设置完成回调

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        // 任务代码
        NSLog(@"1");
    }];
    
    // 设置任务1完成的回调block
    [operation1 setCompletionBlock:^{
            NSLog(@"任务1完成了");
    }];
    

    十一. 监听状态

    • 使用KVO观察对operation状态的监听: isExcuting, isFinished, isCancelled.
    • isExcuting 正在执行任务
    • isFinished 任务完成
    • isCancelled 任务取消执行

    相关文章

      网友评论

        本文标题:iOS 多线程NSOperation

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