[iOS开发]NSOperation、NSOperationQueue

本文详细介绍了NSOperation和NSOperationQueue的使用,包括创建NSInvocationOperation、NSBlockOperation以及自定义NSOperation子类,探讨了操作队列的串行与并发控制,操作依赖以及优先级设置。此外,还讨论了NSOperation与GCD、NSThread在多线程编程中的比较和优缺点。
摘要由CSDN通过智能技术生成

简介概念了解

在SDWebImage中有见过NSOperation这个函数

关于NSOperation我们可以看到官方文档中的解释:
表示于单个任务相关联的代码和数据的抽象类
因为其是一个抽象类,我们不能直接使用它,而是使用它的子类或使用系统定义的子类(NSInvocationOperation或NSBlockOperation)来执行实际的任务。尽管是抽象的,NSOperation的基本实现确实包含了重要的逻辑来协调任务的安全执行。这种内置逻辑的存在允许您将注意力集中在任务的实际实现上,而不是确保它与其他系统对象正确公所所需的粘合代码上。

通常通过将操作添加到操作队列(NSOperationQueue类的实例)来执行操作。

如果不想使用操作队列,可以直接从代码中调用操作的start方法来执行操作。手动执行操作会给代码带来更大的负担,因为启动未处于就绪状态的操作会触发异常。

操作队列是OC中的一种高级的并发处理方法,现在它是基于GCD更高一层的封装,完全面向对象。但是比GCD更加简单易用、代码可读性也更高。

操作队列不同于GCD中FIFO的原则。NSOperationQueue对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作开始执行顺序由操作之间的优先级决定。

操作队列也为我们提供了两种不同类型的队列:主队列和自定队列,主队列运行在主线程之上,而自定义队列在后台执行

为什么我们需要使用NSOperation、NSOperationQueue

  • 可以添加完成的代码块,在操作完成之后执行
  • 添加操作之间的依赖关系,方便的控制执行顺序
  • 设定操作执行的优先级
  • 可以很方便的取消一个操作的执行
  • 使用KVO观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled

基本使用

NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行

使用步骤分为三步

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

之后,系统将队列中的操作取出来执行操作

创建操作

上面说了,我们不能直接使用抽象类,而是使用其子类或系统定义的子类

使用子类NSInvocationOperation(Invocation:调用)

 // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];

- (void)task1 {
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}

在这里插入图片描述
可以看到,操作实在当前线程执行的,没有开启新线程

我们使用NSThread创建线程后自启动新线程

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

在新线程执行操作,结果就是其他线程
也就意味着其只能在自己当前所在的线程中执行同步操作

使用子类NSBlockOperation

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];

一样 也是在当前线程执行同步操作,在其他线程操作则为其他线程

但是其提供了一个方法
addExecutionBlock:^可以为NSBlockOperation添加额外的操作
这些操作可以在不同的线程并发执行
在这里插入图片描述

使用自定义继承自NSOperation的子类

如果使用系统提供的两个子类不能满足日常需求,我们可以使用自定义继承自NSOperation的子类。可以通过重写main或者start方法来定义自己的NSOperation对象

重写main方法
NSOperationTest *op = [[NSOperationTest alloc] init];
    
    [op start];

自定义的Test类中重写main方法
- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}

在这里插入图片描述

重写start方法

- (void)start {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
    [super start];
}

一个道理

创建队列

  • 主队列:在主线程中执行
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 自定义队列:自动放到子线程中执行,同时包含了串行、并发的功能?(什么串行???)
// 自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入到队列中

两种方法

  1. addOperation:将操作加入队列中能够开启新线程,进行并发执行两个任务
    在这里插入图片描述
    在这里插入图片描述

  2. addOperationWithBlock:将操作加入到操作队列后能够开启新线程,进行并发执行
    在这里插入图片描述

NSOperationQueue控制串行、并发

NSOperationQueue有个属性maxConcurrentOperationCount,最大并发数
用来控制一个队列中可以有多少个操作同时参与并发

  • 关于最大并发数maxConcurrentOperationCount
  • maxConcurrentOperationCount默认为-1,表示可以并发执行
  • maxConcurrentOperationCount为1时,串行队列,只能串行执行
  • maxConcurrentOperationCount大于1,队列为并发队列,

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

NSOperation操作依赖

我们可以添加操作之间的依赖关系,通过操作依赖,我们可以很方便的控制操作之间的操作先后顺序

  • addDependency:(NSOperation *)op;添加依赖,使当前操作依赖于操作op的完成
  • removeDependency:(NSOperation *)op;移除依赖,取消当前操作对操作op的依赖
  • @property (readonly, copy) NSArray <NSOperation *> * dependencies在当前操作开始执行之前完成执行的所有操作对象数组
    在这里插入图片描述

使用addOperations

@property (readonly, copy) NSArray <NSOperation *> * dependencies;

waitUntilFinished为YES
数组中的两个线程并发进行
第三个等待前两个结束之后执行 并且第三个使用的是数组中的最后一个线程
在这里插入图片描述
为NO
不等待 三个新线程
在这里插入图片描述

NSOperation优先级

NSOperation提供了优先级属性(queuePriority)。
我们可以通过setQueuePriority方法来改变当前操作在统一队列中的执行优先级


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

前面提到过,对于添加到队列中的操作,首先进入准备就绪的状态,就绪状态取决于操作之间的依赖关系,然后进入就绪状态的操作的开始执行顺序由操作之间的优先级确定(优先级是操作对象自身的属性)

优先级不能取代依赖关系。这也就意味着

假如一个队列中既包含了准备就绪状态的操作(没有任何依赖关系,其就是第一个),又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高,那么虽然准备就绪的操作优先级低,也会优先执行。

也就是官方文档中的:

仅当需要对非相关操作的相对优先级进行分类时,才应使用优先级值。优先级值不应用于实现不同操作对象之间的依赖关系管理

如果优先级一样 就按添加顺序执行(最大线程数为1)
在这里插入图片描述
为什么第一个优先级设置为Low没用,为什么第一个固定与addOperation添加顺序的第一个有关呢
说好的不看添加顺序呢?

NSOperation、NSOperationQueue线程间的通讯

回到主线程

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //需要在主线程执行的代码
}];

线程安全

NSLock来加锁

  1. 初始化NSLock对象
    self.lock = [[NSLock alloc] init]
  2. 上锁
    [self.lock lock]
  3. 解锁
    [self.lock unlock]
    在这里插入图片描述

常用属性和方法

NSOperation

  • 取消操作 cancel
  • 判断操作状态方法
    • isFinished判断操作是否已经结束
    • isCancelled判断操作是否取消
    • isExecuting判断操作是否正在运行
    • isReady判断是否处于就绪状态
  • 操作同步
    • waitUntilFinished阻塞当前线程
    • setCompletionBlock:(void (^)(void))block;当前操作完毕之后执行block
    • addDependency添加依赖
    • removeDependency移除依赖
    • @property (readonly, copy) NSArray<NSOperation *> *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当前在队列中的操作数组
    • operations操作个数
  • 获取队列
    • currentQueue当前队列,如果当前线程不在Queue上,返回nil
    • mainQueue获取主队列

多线程的比较

在这里插入图片描述

  1. pthread
    pthread跨平台,使用难度大,需要手动管理线程生命周期
  2. GCD与NSOperation
  • GCD的执行效率更高,执行的是由Block构成的任务,是一个轻量级的数据结构,写起来更加方便
  • GCD只支持FIFO队列,NSOperation可以通过设置最大并发数、设置优先级、添加依赖关系来调整执行顺序
  • NSOperation可以跨越队列设置依赖关系,GCD仅仅能通过栅栏等方法才能控制执行顺序
  • NSOperation更加面向对象,支持KVO,也可以通过继承等关系添加子类。
    所以如果我们需要考虑异步操作之间的顺序行、依赖关系,比如多线程并发下载等等,就使用NSOperation
  1. GCD 与 NSThread 的区别
  • NSThread 通过 @selector 指定要执行的方法,代码分散, 依靠的是NSObject的分类实现的线程之间的通讯,如果要开线程必须创建多个线程对象。经常只用的是[NSTread current] 查看当前的线程。
  • NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
  • GCD 通过 block 指定要执行的代码,代码集中, 所有的代码写在一起的,让代码更加简单,易于阅读和维护,不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

多线程的优缺点

优点:

使应用程序的响应速度更快,用户界面在进行其他工作的同时仍始终保持活动状态;优化任务执行,适当提高资源利用率(CPU,内存)

缺点:

线程占用内存空间,管理线程需要额外CPU开销,开启大量线程,降低程序性能;增加程序复杂度,如线程间通信,多线程的资源共享等等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值