iOS开发之多线程(4)—— Operation

作者: 看影成痴 | 来源:发表于2020-07-09 16:36 被阅读0次

    文集

    iOS开发之多线程(1)—— 概述
    iOS开发之多线程(2)—— Thread
    iOS开发之多线程(3)—— GCD
    iOS开发之多线程(4)—— Operation
    iOS开发之多线程(5)—— Pthreads
    iOS开发之多线程(6)—— 线程安全与各种锁

    目录

    版本
    简介

    • 操作
    • 同步 & 异步
    • 队列
    • 串行 & 并行

    基本用法

    1. 不使用队列
    2. 使用队列
      2.1 四种添加操作(任务)方法
      2.2 其他属性方法
      2.3 线程间通信

    版本

    Xcode 11.5
    Swift 5.2.2

    简介

    我们先来讨论相关理论知识点, 代码部分统一放到后文.

    Operation基于GCD封装, 完全面向对象. 对应于GCD, Operation也有任务和队列的概念, 只不过在Operation中任务(block)被称为操作(operation).

    Operation支持以下关键功能:

    1. 操作之间添加依赖
    2. 使用KVO监听操作的执行状态
    3. 对操作进行优先级排序
    4. 取消操作

    操作

    • 在GCD中, 任务代码只能写在block中, 并且需要放到队列(dispatch queue)中去执行.
    • 而在Operation中, 操作对象可以单独执行, 也可以添加到队列 (operation queue)去执行.

    Operation是一个抽象类, 代表一个任务. 通常我们使用它的子类NSInvocationOperation或NSBlockOperation来编写任务代码. 当然也可以直接使用Operation, 不过需要重写main方法, 在main里面编写任务代码.

    3种创建方式:

    1. NSInvocationOperation (swift不支持): 此类调用选择器方法(selector), 在方法里面编写任务代码.
    2. NSBlockOperation (swift对应BlockOperation): 此类采用block方式, 在block中编写任务代码.
    3. NSOperation (swift对应Operation): 需要重写main方法, 在main里面编写任务代码.

    2种执行方式:

    1. 不添加到队列, 手动调用operation的start方法.
    2. 添加到队列, 系统自动调用start方法.

    具体如何使用请看下小节.

    同步 & 异步

    在GCD中, 同步和异步分别对应dispatch_sync和dispatch_async方法.
    在Operation中, 没有这种方法.

    苹果文档
    如果计划手动执行操作对象,而不是将其添加到队列中,则可以将操作设计为以同步或异步方式执行。默认情况下,操作对象是同步的。当start()直接从代码中调用同步操作的方法时,该操作将在当前线程中立即执行。
    默认情况下,操作对象以同步方式执行-也就是说,它们在调用其start方法的线程中执行其任务。
    为了获得最佳性能,您应该将操作设计为尽可能异步,使应用程序在执行操作时可以自由地做其他工作。

    • 如果不使用队列, operation默认以同步方式执行. 但我们有办法使之异步执行: 新建一个新线程, 然后在新线程里面调用start方法.
    • 如果使用队列, 系统默认以异步方式执行. 但我们可以使用waitUntilAllOperationsAreFinished (operation queue方法)进行等待, 以确保操作完成后才继续往下执行.

    注意: 与GCD的同步执行不同, 这里虽然设置了等待, 但是一般不在当前线程执行, 而是新开一个线程来执行, 但会保证队列里的所有任务完成后才继续往下执行.

    队列

    队列 (Operation Queue)有两种: 主队列和非主队列 (自定义队列).

    • 主队列通过mainQueue获得, 主队列里的任务都是放到主线程执行 (不包括使用addExecutionBlock:添加的额外操作, 因其可能在其他线程执行).
    • 非主队列 (自定义队列) 即一般 alloc init 出来的队列, 默认在子线程中异步执行. 通多设置最大并发数(maxConcurrentOperationCount)来控制队列是串行还是并发.

    添加操作(任务)到队列有四种方式:

    1. addOperation:
      添加一个现有的Operation (或者其子类).
    2. addOperations:waitUntilFinished:
      可添加多个现有的Operation (或者其子类), 可设置等待所有操作完成后方可继续往下执行.
    3. addOperationWithBlock:
      直接添加一个block
    4. addBarrierBlock:
      添加栅栏, 顺带一个任务. 等栅栏前的所有任务都执行完, 再执行本栅栏的任务, 起到隔离同步等待的目的.

    串行 & 并行

    主队列是串行队列. 自定义队列默认是并发队列, 但可通多设置最大并发数(maxConcurrentOperationCount)来控制队列是串行还是并发.

    maxConcurrentOperationCount
    -1, 默认值, 并发队列;
    =0, 不执行任何操作;
    =1, 串行队列;
    <0, 除-1默认值外, 其他负值均报错;
    >1, 并发队列, 如果数值过大, 最终并发数由系统决定.

    基本用法

    1. 不使用队列

    OC

    #pragma mark - NSInvocationOperation (调用selector方法)
    
    // 使用 NSInvocationOperation
    - (void)useInvocationOperation {
        
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doInvocationOperation) object:nil];
        [operation start];
    }
    
    // 任务
    - (void)doInvocationOperation {
        
        NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
    }
    
    //---------------------------------------------------------------------------------------------------
    //log:
    //-[ViewController doInvocationOperation], thread:<NSThread: 0x6000017ec2c0>{number = 1, name = main}
    
    
    #pragma mark - NSBlockOperation (使用block)
    
    // 使用 NSBlockOperation
    - (void)useBlockOperation {
        
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
        }];
        
        [operation start];
    }
    
    //---------------------------------------------------------------------------------------------------
    //log:
    //-[ViewController useBlockOperation]_block_invoke, thread:<NSThread: 0x600000760100>{number = 1, name = main}
    
    
    #pragma mark - 自定义Operation (重写main)
    
    - (void)useCustomOperation {
        
        MyOperation *operation = [[MyOperation alloc] init];
        [operation start];
    }
    
    //---------------------------------------------------------------------------------------------------
    //log:
    //-[MyOperation main], thread:<NSThread: 0x600000618280>{number = 1, name = main}
    
    MyOperation.m
    
    @implementation MyOperation
    
    - (void)main {
        
        NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
    }
    
    @end
    

    在自定义Operation中, 调用start方法后, 系统会执行多项安全检查, 最终会调用main方法.

    Swift

    //MARK: - 使用 BlockOperation (block)
    @objc func useBlockOperation() {
        
        let operation = BlockOperation.init {
            print("\(#function), thread:\(Thread.current)")
        }
        operation.start()
    }
        
    //---------------------------------------------------------------------------------------------------
    //log:
    //useBlockOperation(), thread:<NSThread: 0x600003f78000>{number = 1, name = main}
        
    
    //MARK: - 使用 自定义Operation (重写main)
    @objc func useCustomOperation() {
        
        let operation = CustomOperation.init()
        operation.start()
    }
        
    //---------------------------------------------------------------------------------------------------
    //log:
    //main(), thread:<NSThread: 0x600003ec8cc0>{number = 1, name = main}
    
    class CustomOperation: Operation {
        
        override func main() {
            print("\(#function), thread:\(Thread.current)")
        }
    }
    

    2. 使用队列

    2.1 四种添加操作(任务)方法

    OC

        NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
        // 1 addOperation:
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op1 thread:%@", [NSThread currentThread]);
        }] ;
        [operationQueue addOperation:op1];
    
        // 2 addOperations:waitUntilFinished:
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op2 thread:%@", [NSThread currentThread]);
        }] ;
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op3 thread:%@", [NSThread currentThread]);
        }] ;
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op4 thread:%@", [NSThread currentThread]);
        }] ;
        [operationQueue addOperations:@[op2, op3, op4] waitUntilFinished:YES];
        NSLog(@"queue finished");
    
        // 3 addOperationWithBlock:
        [operationQueue addOperationWithBlock:^{
            NSLog(@"block thread:%@", [NSThread currentThread]);
        }];
    
        // 4 addBarrierBlock:
        [operationQueue addBarrierBlock:^{
            NSLog(@"barrier thread:%@", [NSThread currentThread]);
        }];
    
    //---------------------------------------------------------------------------------------------------
    //log:
    //op2 thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}
    //op4 thread:<NSThread: 0x600002eaf180>{number = 4, name = (null)}
    //op1 thread:<NSThread: 0x600002ed77c0>{number = 5, name = (null)}
    //op3 thread:<NSThread: 0x600002eaf140>{number = 3, name = (null)}
    //queue finished
    //block thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}
    

    Swift

        let operationQueue = OperationQueue.init()
            
        // 1 addOperation(_ op: Operation)
        let op1 = BlockOperation.init {
            print("op1, thread:\(Thread.current)")
        }
        operationQueue.addOperation(op1)
        
        
        // 2 addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
        let op2 = BlockOperation.init {
            print("op2, thread:\(Thread.current)")
        }
        let op3 = BlockOperation.init {
            print("op3, thread:\(Thread.current)")
        }
        let op4 = BlockOperation.init {
            print("op4, thread:\(Thread.current)")
        }
        operationQueue.addOperations([op2, op3, op4], waitUntilFinished: true)
        
        
        // 3 addOperation(_ block: @escaping () -> Void)
        operationQueue.addOperation {
            print("block, thread:\(Thread.current)")
        }
        
    
        // 4 addBarrierBlock(_ barrier: @escaping () -> Void)
        operationQueue.addBarrierBlock {
            print("barrier, thread:\(Thread.current)")
        }
            
    //---------------------------------------------------------------------------------------------------
    log:
    op3, thread:<NSThread: 0x600002399200>{number = 4, name = (null)}
    op1, thread:<NSThread: 0x6000023c34c0>{number = 5, name = (null)}
    op2, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
    op4, thread:<NSThread: 0x6000023d42c0>{number = 7, name = (null)}
    block, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
    barrier, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
    
    2.2 其他属性方法

    maxConcurrentOperationCount

    - (void)setOperationCount {
        
        NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
        
        NSLog(@"start");
        
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op1 thread:%@", [NSThread currentThread]);
        }] ;
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op2 thread:%@", [NSThread currentThread]);
        }] ;
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op3 thread:%@", [NSThread currentThread]);
        }] ;
        
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"op4 thread:%@", [NSThread currentThread]);
        }] ;
        
        /**
         -1, 默认值, 并发队列;
         =0, 不执行任何操作;
         =1, 串行队列;
         <0, 除-1默认值外, 其他负值均报错;
         >1, 并发队列, 如果数值过大, 最终并发数由系统决定.
         */
        operationQueue.maxConcurrentOperationCount = 1;
        [operationQueue addOperations:@[op1, op2, op3, op4] waitUntilFinished:YES];
        
        NSLog(@"end");
    }
    
    //---------------------------------------------------------------------------------------------------
    log:
    start
    op1 thread:<NSThread: 0x600000b181c0>{number = 5, name = (null)}
    op2 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
    op3 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
    op4 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
    end
    

    我们看到:
    由于是串行队列, 没有添加依赖和设置优先级, 所以会按照1~4顺序执行;
    又因为设置了等待(waitUntilFinished:YES), 所以end最后才打印;
    开不开新线程, 由系统决定.

    添加依赖

    addDependency:
    

    设置优先级

    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    

    取消队列里所有任务

    - (void)cancelAllOperations;
    
    2.3 线程间通信
    - (void)callMainQueue {
        
        NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
        
        [operationQueue addOperationWithBlock:^{
            
            NSLog(@"block, thread:%@", [NSThread currentThread]);
            
            // call main queue
            [NSOperationQueue.mainQueue addOperationWithBlock:^{
                
                NSLog(@"main, thread:%@", [NSThread currentThread]);
            }];
        }];
    }
    
    //---------------------------------------------------------------------------------------------------
    log:
    block, thread:<NSThread: 0x600003ee0580>{number = 6, name = (null)}
    main, thread:<NSThread: 0x600003ea0240>{number = 1, name = main}
    

    demo
    https://github.com/LittleLittleKang/KKThreadsDemo

    相关文章

      网友评论

        本文标题:iOS开发之多线程(4)—— Operation

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