iOS多线程 之 NSOperation详解

2020-03-24  本文已影响0人  有梦想的狼

简介

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

优势

  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便的控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO观察对操作执行状态的更改:isExecuteingisFinishedisCancelled

操作和操作队列

使用步骤

NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue,我们能更好的实现异步执行。

1. 创建操作:

NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们可以使用三种方式来封装操作,NSInvocationOperationNSBlockOperation自定义继承自 NSOperation 的子类

2. 创建队列:

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

3. 将操作加入到队列中:

上面我们说到NSOperation需要配合NSOperationQueue来实现多线程

那么我们需要将创建的操作加入到队列中去,总共有两种方法。

NSOperationQueue控制串行执行,并发执行

上面我们说过,NSOperationQueue创建的自定义队列同时具备串行,并发功能,上面我们演示了并发功能,那么其他的串行功能如何实现的?

这里有个关键属性maxConcurrentOperationCount,叫做最大并发操作数,用来控制一个特定队列中可以有多少个操作同时参与并发执行。

注意:这里maxConcurrentOperationCount控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。

NSOperation 操作依赖

NSOperationNSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。

现有一个需求,有 A,B 两个需求,其中 A 执行完操作,B 才能执行操作。
如果使用依赖来处理的话,那么就需要让操作 B 依赖于操作 A。代码如下

/**
 添加操作依赖
 */
- (void)addDependency {
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%d---%@",i, [NSThread currentThread]); // 打印当前线程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%d---%@",i, [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    // 添加依赖
    [op2 addDependency:op1];    // 让 op2依赖于 op1.则先执行 op1,再执行 op2
    
    // 添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

输出结果为:

2020-03-18 15:56:52.028698+0800 多线程[15645:999754] 1---0---<NSThread: 0x600002cc34c0>{number = 3, name = (null)}
2020-03-18 15:56:54.030698+0800 多线程[15645:999754] 1---1---<NSThread: 0x600002cc34c0>{number = 3, name = (null)}
2020-03-18 15:56:56.035021+0800 多线程[15645:999754] 2---0---<NSThread: 0x600002cc34c0>{number = 3, name = (null)}
2020-03-18 15:56:58.040238+0800 多线程[15645:999754] 2---1---<NSThread: 0x600002cc34c0>{number = 3, name = (null)}

可以看出:通过添加操作依赖,无论运行 n 多次,其结果都是 op1先执行,op2后执行。

NSOperation 优先级

NSOperation 提供了queuePriority(优先级)属性,queuePriority 属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

上边我们说过:对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)

那么,什么样的操作才是进入就绪状态的操作呢?

举个例子,现在有4个优先级都是 NSOperationQueuePriorityNormal(默认级别)的操作:op1,op2,op3,op4。其中 op3 依赖于 op2,op2 依赖于 op1,即 op3 -> op2 -> op1。现在将这4个操作添加到队列中并发执行。

理解了进入就绪状态的操作,那么我们就理解了queuePriority属性的作用对象。

NSOperation,NSOperationQueue 线程间的通信

在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

/**
 线程间通信
 */
- (void)communication {
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 添加操作
    [queue addOperationWithBlock:^{
        // 异步执行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%d---%@",i, [NSThread currentThread]); // 打印当前线程
        }
        
        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%d---%@",i, [NSThread currentThread]); // 打印当前线程
            }
        }];
    }];
}

输出结果为:

2020-03-18 16:26:54.990224+0800 多线程[15909:1020280] 1---0---<NSThread: 0x6000031bee00>{number = 3, name = (null)}
2020-03-18 16:26:56.992269+0800 多线程[15909:1020280] 1---1---<NSThread: 0x6000031bee00>{number = 3, name = (null)}
2020-03-18 16:26:58.993433+0800 多线程[15909:1020170] 2---0---<NSThread: 0x6000031e10c0>{number = 1, name = main}
2020-03-18 16:27:00.994794+0800 多线程[15909:1020170] 2---1---<NSThread: 0x6000031e10c0>{number = 1, name = main}

可以看出:通过线程间的通信,先在其他线程中执行操作,等操作执行完了之后再回到主线程执行主线程的相应操作。

NSOperation、NSOperationQueue 线程同步和线程安全

举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。

下面,我们模拟火车票售卖的方式,实现 NSOperation 线程安全和解决线程同步问题。
场景:总共有40张火车票,有两个售卖火车票的窗口,一个是广州火车票售卖窗口,另一个是龙岩火车票售卖窗口。两个窗口同时售卖火车票,卖完为止

NSOperation 常用属性和方法:

NSOperationQueue 常用属性和方法:

上一篇下一篇

猜你喜欢

热点阅读