• 【iOS】—— NSOperation、NSOperationQueue


    一、概念

    NSOperation、NSOperationQueue是苹果提供给我们的一套多线程解决方案。
    实际上NSOperation、NSOperationQueue是基于GCD更高一层的封装,完全面向对象。
    比GCD更简单易用、代码可读性也更高。

    1.为什么要使用NSOperation、NSOperationQueue?

    • 可添加完成的代码块,在操作完成之后执行
      NSOperation的completionBlock属性
    • 添加操作之间的依赖关系,方便的控制执行顺序
    • 设定操作执行的优先级
    • 可以很方便的取消一个操作的执行
    • 使用KVO观察对操作执行状态的更改executing、finished、cancelled
      通过KVO的方式移除finished值为yes的NSOperation
    • 可设置最大并发操作数,来控制串行、并发

    2.操作和操作队列

    NSOperation是基于GCD的更高一层的封装,GCD中的一些概念同样适用于NSOperation、NSOperationQueue。
    在NSOperation、NSOperationQueue中也有类似任务(操作)、队列(操作队列)的概念:

    • 操作(Operation)
      • 执行操作的意思,就是在线程中执行的那段代码
      • 在GCD中是放在Block中的。在NSOperation中,我们使用NSOperation子类NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作
    • 操作队列(Operation Queues)
      • 操作队列,即用来存放操作的队列。不同于GCD中的调度队列FIFO(先进先出)的原则。NSOperationQueue对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于与操作之间的依赖关系),然后进入就绪状态的这些操作,开始执行的顺序由操作之间的优先级决定。(优先级是操作对象自身的属性)

    3.NSOperation、NSOperationQueue常用属性和方法归纳

    NSOperation常用属性和方法

    • 取消操作cancel
    • 判断操作状态方法
      • isFinished判断操作是否已经结束
      • isCancelled判断操作是否取消
      • isExecuting判断操作是否正在运行
      • isReady判断是否处于就绪状态
      • isAsynchronous表示任务是并发还是同步执行
    • 操作同步
      • waitUntilFinished阻塞当前线程
      • setCompletionBlock:(void (^)(void))block;当前操作完毕之后执行block
      • addDependency添加依赖
      • removeDependency移除依赖
      • @property (readonly, copy) NSArray *dependencies;数组存储操作

    NSOperationQueue常用属性和方法

    • 取消/暂停/恢复队列中的操作
      • cancelAllOperations;取消队列中所有的操作
      • isSuspended判断队列是否处理暂停状态 YES:暂停状态,NO恢复状态
      • setSuspended:(BOOL)b;设置操作的暂停和恢复 YES:暂停,NO:恢复
    • 同步操作
      • waitUntilAllOperationsAreFinished;阻塞当前线程,直到队列中的操作全部完成
    • 添加/获取
      • addOperationWithBlock:(void (^)(void))block向队列中添加一个NSBlockOperation类型的操作对象
      • addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;添加操作数组,wait标志是否阻塞当前线程知道所有操作结束
      • operations当前在队列中的操作数组
      • operationCount操作个数
    • 获取队列
      • currentQueue当前队列,如果当前线程不在Queue上,返回nil
      • mainQueue获取主队列

    注意

    暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
    暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

    二、NSOperation、NSOperationQueue的使用

    1.使用步骤

    NSOperation实现多线程的使用步骤分为三步:

    • 1.创建操作:先将需要执行的操作封装到一个NSOperation对象中
    • 2.创建队列:创建NSOperationQueue对象
    • 3.将操作加入到队列中:将NSOperation对象添加到NSOperationQueue对象中

    之后,系统会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

    2.基本使用

    2.1 创建操作

    因为NSOperation是个抽象类,不能创建实例,所以我们通常使用它的子类来进行封装操作:

    • 使用子类NSInvocationOperation
    • 使用子类NSBlockOperation
    • 自定义继承自NSOperation的子类,通过实现内部相应的方法来封装操作

    使用子类NSInvocationOperation

    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOp) object:nil];
    // 2.调用 start 方法开始执行操作
    [op start];
    
    - (void)testOp {
        NSLog(@"testOp--%@", [NSThread currentThread]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    输出结果:
    432423423

    • 因为没有使用NSOperationQueue,并且我们是在当前线程(主线程)中执行一个操作,所以它是在当前线程(主线程)中完成操作的,并没有开启新线程。
    • 如果在其他线程中执行操作,则打印的结果为其他线程。总之,它是在那个线程创建并启动的,它就会在执行

    使用子类NSBlockOperation

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op---%@", [NSThread currentThread]);
    }];
    // 2.调用 start 方法开始执行操作
    [op start];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出结果:
    564535

    • 这个其实就和上边NSInvocationOperation一样,只是使用block之后更加简洁了,也没有开启新线程,哪个线程创建并执行就是那个线程

    NSBlockOperation还有一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作。这些操作(包括blockOperationWithBlock中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成

    如果添加的操作多的话,blockOperationWithBlock:中的操作也有可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到blockOperationWithBlock:中的操作一定在当前线程中执行

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op---%@", [NSThread currentThread]);
    }];
    // 2.添加额外操作
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1--Block task, %@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2--Block task, %@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3--Block task, %@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4--Block task, %@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"5--Block task, %@", [NSThread currentThread]);
    }];
    // 3.调用 start 方法开始执行操作
    [op start];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    输出结果:
    4234234

    • 使用子类NSBlockOperation,并调用addexecutionBlock:的情况下,blockOperationWithBlock:方法中的操作和额外加的操作是在不同线程中异步执行的。同时,额外操作多的时候,blockOperationWithBlock:方法中的操作有可能不会在当前线程中执行。
    • 开启的线程数是由系统来决定的。

    注意:addExecutionBlock: 方法必须在start()方法之前执行,否则就会报错。

    使用继承自NSOperation自定义子类

    我们可以通过自定义继承自NSOperation的子类,重写main或者start来定义自己的NSOperation对象。

    • 如果只是重写了main方法,有底层控制变更任务执行、完成状态以及任务退出。
    • 如果重写了start方法,需要自己控制任务状态。

    重写main方法比较简单,我们不需要管理线程的状态属性executing(是否正在执行)和finished(是否完成)。当main执行完返回的时候,这个操作就结束了。

    重写main方法
    // .h 文件
    #import <Foundation/Foundation.h>
    
    @interface NSOperationTest : NSOperation
    @end
    
    // .m 文件
    #import "NSOperationTest.h"
    
    @implementation NSOperationTest
    - (void)main {
        if (!self.isCancelled) {
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"test---%@", [NSThread currentThread]);
            }
        }
    }
    @end
    
    // 定义并执行
    NSOperationTest *test = [[NSOperationTest alloc] init];
    NSLog(@"%d", test.finished);
    [test start];
    NSLog(@"%d", test.finished);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    输出结果:
    423423423

    重写start方法
    // .h 文件
    #import <Foundation/Foundation.h>
    
    @interface NSOperationTest : NSOperation
    @end
    
    // .m 文件
    #import "NSOperationTest.h"
    
    @implementation NSOperationTest
    - (void)start {
        if (!self.isCancelled) {
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"test---%@", [NSThread currentThread]);
            }
        }
    }
    @end
    
    // 定义并执行
    NSOperationTest *test = [[NSOperationTest alloc] init];
    NSLog(@"%d", test.finished);
    [test start];
    NSLog(@"%d", test.finished);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    输出结果:
    423423423

    • 我们明显能看到,重写start方法系统没有自动管理线程的状态属性。
    • 我们没有使用NSOperationQueue,在主线程中创建自定义子类的操作对象并执行,并没有开启新线程。

    2.2 创建队列

    NSOperationQueue共有两种队列:主队列、自定义队列,其中自定义队列同时包含了串行、并发功能。

    • 主队列:凡是添加到主队列中的操作,都会放到主线程中执行。
    • 自定义队列(非主队列):添加到这种队列中的操作,会自动放到子线程中执行,同时包含了 串行、并发 功能。
    // 主队列获取方法
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    // 自定义队列(非主队列)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 将操作加入队列中

    NSOperation需要配合NSOperationQueue才能实现多线程,将创建好的操作加入到队列中,有两种方法:

    - (void)addOperation:(NSOperation *)op;方法

    创建好队列、操作,再将操作都加到队列中:

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.创建操作
    NSInvocationOperation *firstOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationFirst) object:nil];
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    [secondOperation addExecutionBlock:^{
        NSLog(@"add--%@", [NSThread currentThread]);
    }];
    NSInvocationOperation *thirdOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationThird) object:nil];
    
    // 3.将操作加到队列中
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];
    
    - (void)operationFirst {
        NSLog(@"1---%@", [NSThread currentThread]);
    }
    - (void)operationThird {
        NSLog(@"3---%@", [NSThread currentThread]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    输出结果:
    4234234

    • 我们可以看到,添加到队列中的操作开启了新线程来完成。

    - (void)addOperationWithBlock:(void (^)(void)block);方法

    不用创建操作,直接在block中添加操作,将block加入到队列中:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出结果:
    423423423

    • 与上述方法相同,不过使用block看起来更简洁了。

    但是,我们看上面的例子,基本上都是并行执行的,下面就来说说怎么控制并发和串行:

    3.NSOperationQueue控制串行执行、并行执行

    操作队列有一个属性,最大并发操作数,用来控制一个特定的队列中可以有多少个操作同时并发执行,也就是一个队列中同时能并发执行的最大操作数:

    @property NSInteger maxConcurrentOperationCount;
    
    • 1
    • maxConcurrentOperationCount默认为-1,表示不进行限制,可进行并发执行
    • maxConcurrentOperationCount为1时,此时队列为串行队列,只能串行执行
    • maxConcurrentOperationCount大于1时,队列为并发队列。操作并发执行。当这个值超过了系统限制,就会自动调整为系统设定的值。
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //queue.maxConcurrentOperationCount = 1;
    queue.maxConcurrentOperationCount = 2;
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"5---%@", [NSThread currentThread]);
    }];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出结果:

    • 最大并发数为1:

    • 43242342

    • 最大并发数为2:
      4234234

    • 可能从输出结果上看不出什么,主要是输出的过程,你可以自己用上述代码试试,当为 1 的时候,明显能看出来每个操作之间都有2秒的延时,但是为 2 的时候,它延迟两秒之后几乎同时输出的。

    • 对于开启线程数,是由系统决定的,不需要我们来管理。

    4.NSOperation操作依赖

    NSOperation、NSOperationQueue最吸引人的就是它能够添加操作之间的依赖关系,通过依赖关系,我们就可以很方便的控制操作之间的执行先后顺序。

    NSOperation提供了3个接口供我们使用依赖

    • - (void)addDependency:(NSOperation *)op添加依赖,是当前操作依赖op的完成,op完成之后才会执行当前操作。
    • - (void)removeDependency:(NSOperation *)op移除依赖,取消当前操作对操作op的依赖。
    • @property (readonly, copy) NSArray *dependencies;操作对象的一个属性,在当前操作开始执行之前完成执行的所有操作对象数组。

    没有添加依赖时:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
    NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"firstOperation");
    }];
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"secondOperation");
    }];
    NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"thirdOperation");
    }];
    
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出结果:
    3123123

    • 没有添加依赖,执行都是并发执行的,没有次序可言。

    添加依赖后:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"firstOperation");
    }];
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"secondOperation");
    }];
    NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"thirdOperation");
    }];
    
    [secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
    [thirdOperation addDependency:secondOperation]; // 让thirdOperation依赖于secondOperation,即secondOperation先执行,在执行thirdOperation
    
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出结果:
    34234234

    • 在我们添加依赖之后,操作都是按照添加依赖的次序来的。

    相互依赖:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
    NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"firstOperation");
    }];
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"secondOperation");
    }];
    NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"thirdOperation");
    }];
    
    [secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
    [firstOperation addDependency:secondOperation]; // 让firstOperation依赖于secondOperation,即secondOperation先执行,在执行firstOperation
    
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出结果:
    4234234

    • 在添加相互依赖之后,这两个都不执行了。

    5.NSOperation优先级

    依赖只是一种执行关系罢了,NSOperation还为我们专门提供了优先级属性,我们可以通过setQueuePriority:方法来设置同一队列中操作的优先级,下面是系统给定的优先级(默认为NSOperationQueuePriorityNormal):

    // 优先级的取值
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对于添加到队列中的操作,首先进入准备就绪的状态,然后进入就绪状态的操作的开始执行顺序由操作之间的相对的优先级决定。

    就绪状态取决于操作之间的依赖关系,也就是只有这个操作的依赖操作完成了,该操作才会处于就绪状态。

    举个例子:
    当有四个优先级都是NSOperationQueuePriorityNormal(默认优先级)的操作:op1、op2、op3、op4,op2依赖于op3,op3依赖于op4。

    • 其中只有op1、op4没有需要依赖的操作,所以op1、op4就是处于准备就绪状态的操作。
    • op2、op3都有依赖的操作,所以op2、op3都不是准备就绪的操作。
    • 当op4完成时,op3进入就绪状态;当op3完成时,op2进入就绪状态。

    queuePriority

    • queuePriority属性决定了已进入就绪状态下的操作之间的开始执行顺序。优先级不能取代依赖关系,该属性仅决定开始执行顺序,并不能保证完成执行顺序
    • 如果一个队列中既包含高优先级操作、也有低优先级操作,并且这两个操作都已经准备就绪,那么队列就会先执行高优先级操作。
    • queuePriority属性决定的是进入就绪状态下的操作之间的开始执行顺序,并不保证执行完成顺序。而依赖则是控制两个操作之间的执行顺序,使一个操作在它依赖的操作执行完成之后再开始执行。
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"begin firstOperation");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"firstOperation end");
    }];
    firstOperation.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"begin secondOperation");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"secondOperation end");
    }];
    secondOperation.queuePriority = NSOperationQueuePriorityHigh;
    
    NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"begin thirdOperation");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thirdOperation end");
    }];
    thirdOperation.queuePriority = NSOperationQueuePriorityNormal;
    
    queue.maxConcurrentOperationCount = 3;
    
    
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出结果:
    4234234

    • 如果我们将最大操作执行数设置为1,那么队列中操作数将会按添加的顺序串行执行,一个操作执行完才会执行另一个操作。
    • 如果队列中所有的操作的优先级相同,并且也进入就绪状态,那么执行的顺序就按照提交到队列的顺序执行。否则,队列总是执行相对于其他就绪操作优先级更高的操作。
    问题:为什么我们设置了优先级,他还是不会按照优先级大小来执行操作?

    可以通过NSOperation 的queuePriority属性来设置操作在队列中的执行优先级:

    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    
    @property NSOperationQueuePriority queuePriority;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以通过NSOperation 的qualityOfService属性来设置操作在队列中的服务质量(iOS8 以后苹果推荐使用服务质量替代优先级):

    typedef NS_ENUM(NSInteger, NSQualityOfService) {
        NSQualityOfServiceUserInteractive = 0x21,
        NSQualityOfServiceUserInitiated = 0x19,
        NSQualityOfServiceUtility = 0x11,
        NSQualityOfServiceBackground = 0x09,
        NSQualityOfServiceDefault = -1
    } 
    
    @property NSQualityOfService qualityOfService;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实例:

        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务1,%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务2,%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务3,%@",[NSThread currentThread]);
        }];
        // 设置优先级
        op1.queuePriority = NSOperationQueuePriorityLow;
        op2.queuePriority = NSOperationQueuePriorityHigh;
        op3.queuePriority = NSOperationQueuePriorityNormal;
        // 将操作加入队列
        [queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
    /*
    2022-08-01 15:42:45.682888+0800 NSTread[52469:2149184] 执行任务3, {number = 3, name = (null)}
    2022-08-01 15:42:45.682889+0800 NSTread[52469:2149181] 执行任务1, {number = 4, name = (null)}
    2022-08-01 15:42:45.682889+0800 NSTread[52469:2149180] 执行任务2, {number = 6, name = (null)}
     */
    
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务1,%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务2,%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"执行任务3,%@",[NSThread currentThread]);
        }];
        // 设置服务质量
        op1.qualityOfService = NSQualityOfServiceBackground;
        op2.qualityOfService = NSQualityOfServiceUserInitiated;
        op3.qualityOfService = NSQualityOfServiceUtility;
        // 将操作加入队列
        [queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
    /*
    2022-08-01 15:39:12.408928+0800 NSTread[52411:2145432] 执行任务2, {number = 7, name = (null)}
    2022-08-01 15:39:12.408933+0800 NSTread[52411:2145434] 执行任务3, {number = 6, name = (null)}
    2022-08-01 15:39:12.409001+0800 NSTread[52411:2145433] 执行任务1, {number = 5, name = (null)}
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 经过我的测试,发现优先级和服务质量都不能绝对保证代码的优先执行情况,这就关系到优先级反转了,可以自己下去了解一下。
      总之,设置优先级和服务质量都不能保证代码执行顺序的绝对性,只是改变其先执行或后执行的概率。

    如果我们要确保操作的执行的先后顺序,即操作间有同步关系,我们应该使用依赖关系来确保绝对的执行顺序,即使操作对象位于不同的操作队列中。在操作对象的所有依赖操作完成执行之前,操作对象不会被视为已准备好执行。

    6.NSOperation、NSOperationQueue线程间的通信

    在iOS开发过程中,我们一般要在主线程中进行UI刷新,通常把一些耗时的操作放在其他线程中,当其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程间的通信:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d--%@", i, [NSThread currentThread]);
        }
        
        // 回到主线程进行操作
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"main---%@", [NSThread currentThread]);
        }];
    }];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出结果:
    5345345

    三、多线程的比较

    4234234

    1.pthread

    • pthread跨平台,使用难度大,需要手动管理线程生命周期

    2.GCD与NSOperation

    • GCD的执行效率更高,执行的是由Block构成的任务,是一个轻量级的数据结构,写起来更加方便
    • GCD只支持FIFO队列,NSOperation可以通过设置最大并发数、设置优先级、添加依赖关系来调整执行顺序
    • NSOperation可以跨越队列设置依赖关系,GCD仅仅能通过栅栏等方法才能控制执行顺序
    • NSOperation更加面向对象,支持KVO,也可以通过继承等关系添加子类。

    所以如果我们需要考虑异步操作之间的顺序行、依赖关系,比如多线程并发下载等等,就使用NSOperation。

    3.GCD 与 NSThread 的区别

    • NSThread 通过 @selector 指定要执行的方法,代码分散, 依靠的是NSObject的分类实现的线程之间的通讯,如果要开线程必须创建多个线程对象。经常只用的是[NSTread currentThread] 查看当前的线程。
    • NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
    • GCD 通过 block 指定要执行的代码,代码集中, 所有的代码写在一起的,让代码更加简单,易于阅读和维护,不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

    4.多线程的优缺点

    • 优点
      • 使应用程序的响应速度更快,用户界面在进行其他工作的同时仍始终保持活动状态;
      • 优化任务执行,适当提高资源利用率(CPU,内存);
    • 缺点
      • 线程占用内存空间,管理线程需要额外CPU开销,开启大量线程,降低程序性能;
      • 增加程序复杂度,如线程间通信,多线程的资源共享等等;

    参考我学姐的博客

  • 相关阅读:
    Linux系列之查找命令
    FRED应用:TMT MOBIE成像光谱仪的概念设计阶段杂散光分析
    快收藏!最适合计算机大学生的Java毕业设计项目--高校食堂点餐系统
    ERINE系列论文解读
    linux基本指令(上)
    【附源码】计算机毕业设计JAVA医院住院部管理
    基于springboot实现滴答拍摄影项目【项目源码+论文说明】计算机毕业设计
    python有哪些编译器
    判断链表是否是回文链表
    强大的Nginx配置生成器
  • 原文地址:https://blog.csdn.net/m0_55124878/article/details/126094371