MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

深入学习Objective-C中NSOperationQueue的语法与使用

2024-08-031.7k 阅读

1. NSOperationQueue 简介

NSOperationQueue 是 Objective-C 中用于管理操作队列的类,它提供了一种方便的方式来异步执行任务。在 iOS 和 macOS 开发中,多线程编程是非常重要的,NSOperationQueue 使得开发者可以轻松地管理和调度一系列的操作,而无需手动管理线程。

NSOperationQueue 基于队列的机制,按照一定的规则(如先进先出,或者根据操作的优先级)来执行添加到队列中的操作。它允许开发者将复杂的任务分解为多个较小的操作,然后将这些操作添加到队列中,系统会自动在后台线程中执行这些操作,从而提高应用程序的响应性和性能。

2. NSOperation 基础

在深入了解 NSOperationQueue 之前,我们先来认识一下 NSOperation。NSOperation 是一个抽象类,它定义了一个操作的基本属性和方法。在实际使用中,我们通常使用它的子类或自定义子类来创建具体的操作。

2.1 NSOperation 子类

  • NSInvocationOperation:它可以封装一个方法调用,并在后台线程中执行该方法。例如:
// 创建一个 NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myMethod) object:nil];
// 执行操作
[operation start];

这里 myMethod 是当前类中定义的一个方法,NSInvocationOperation 会在后台线程启动并调用这个方法。

  • NSBlockOperation:允许将多个 block 作为操作添加进来,并可以并发执行这些 block。示例代码如下:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 第一个 block 中的代码
    NSLog(@"第一个 block 执行");
}];
[blockOperation addExecutionBlock:^{
    // 第二个 block 中的代码
    NSLog(@"第二个 block 执行");
}];
[blockOperation start];

上述代码中,两个 block 会在后台线程中并发执行(具体是否并发取决于系统调度)。

2.2 自定义 NSOperation 子类

除了使用系统提供的子类,我们还可以自定义 NSOperation 子类。自定义子类需要重写 main 方法,该方法包含了操作要执行的具体任务。例如:

@interface MyOperation : NSOperation
@end

@implementation MyOperation
- (void)main {
    if (!self.isCancelled) {
        // 具体的任务代码
        NSLog(@"自定义操作执行");
    }
}
@end

然后可以像使用其他操作一样使用自定义的操作:

MyOperation *myOp = [[MyOperation alloc] init];
[myOp start];

3. NSOperationQueue 基本操作

3.1 创建 NSOperationQueue

创建 NSOperationQueue 非常简单,只需要使用它的构造方法即可。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

另外,每个应用程序都有一个主队列 [NSOperationQueue mainQueue],主队列是与主线程相关联的队列,添加到主队列的操作会在主线程中执行。这对于更新 UI 等必须在主线程执行的操作非常有用。例如:

// 在主线程执行操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    // 更新 UI 的代码
    self.label.text = @"更新后的文本";
}];

3.2 添加操作到队列

添加操作到 NSOperationQueue 有几种方式。

  • addOperation: 方法:可以直接添加一个 NSOperation 对象到队列。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myMethod) object:nil];
[queue addOperation:operation];
  • addOperationWithBlock: 方法:方便地将一个 block 作为操作添加到队列。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
    // 操作的代码
    NSLog(@"在队列中执行的 block 操作");
}];
  • addOperations:waitUntilFinished: 方法:可以一次性添加多个操作到队列。如果 waitUntilFinished 参数为 YES,则当前线程会等待所有操作执行完毕才继续执行后续代码;如果为 NO,则当前线程不会等待,继续执行后续代码。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(method1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(method2) object:nil];
NSArray<NSOperation *> *operations = @[op1, op2];
[queue addOperations:operations waitUntilFinished:NO];

4. 操作之间的依赖关系

NSOperation 可以设置依赖关系,使得一个操作在另一个或多个操作完成后才开始执行。这在处理一些有先后顺序要求的任务时非常有用。

4.1 设置依赖关系

通过 addDependency: 方法可以为一个操作添加依赖。例如:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 1 执行");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 2 执行");
}];
// 设置操作 2 依赖于操作 1
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];

在上述代码中,操作 2 会在操作 1 完成后才开始执行。

4.2 移除依赖关系

如果需要移除已经设置的依赖关系,可以使用 removeDependency: 方法。例如:

[operation2 removeDependency:operation1];

这样操作 2 就不再依赖于操作 1,它的执行不再受操作 1 完成状态的影响。

5. 操作的优先级

NSOperationQueue 允许为操作设置优先级,优先级高的操作会优先被执行。操作的优先级分为以下几个级别:

  • NSOperationQueuePriorityVeryLow:非常低的优先级。
  • NSOperationQueuePriorityLow:低优先级。
  • NSOperationQueuePriorityNormal:正常优先级(默认)。
  • NSOperationQueuePriorityHigh:高优先级。
  • NSOperationQueuePriorityVeryHigh:非常高的优先级。

5.1 设置操作优先级

通过操作对象的 queuePriority 属性可以设置优先级。例如:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 1 执行");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 2 执行");
}];
// 设置操作 1 的优先级为非常高
operation1.queuePriority = NSOperationQueuePriorityVeryHigh;
// 设置操作 2 的优先级为非常低
operation2.queuePriority = NSOperationQueuePriorityVeryLow;
[queue addOperation:operation1];
[queue addOperation:operation2];

在这个例子中,操作 1 由于优先级高,会优先于操作 2 执行。

6. 操作队列的最大并发数

NSOperationQueue 有一个属性 maxConcurrentOperationCount,用于设置队列中最大的并发操作数。

6.1 设置最大并发数

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发数为 2
queue.maxConcurrentOperationCount = 2;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 1 执行");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 2 执行");
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 3 执行");
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];

在上述代码中,队列最多同时执行 2 个操作。如果有新的操作加入,当当前执行的操作数达到最大并发数时,新操作会等待直到有操作完成,才会被执行。

6.2 调整最大并发数

在运行时可以根据需要调整最大并发数。例如:

// 动态调整最大并发数为 3
queue.maxConcurrentOperationCount = 3;

这样队列就可以同时执行 3 个操作,更多的操作可以并行执行,提高了任务的执行效率。

7. 操作的状态与生命周期

NSOperation 有几种状态,反映了操作的执行情况。

7.1 操作的状态

  • isReady:表示操作是否准备好执行。当操作的所有依赖都已完成,并且没有被取消时,操作就处于 ready 状态。
  • isExecuting:表示操作正在执行。
  • isFinished:表示操作已经完成执行。
  • isCancelled:表示操作已经被取消。

7.2 操作的生命周期

  1. 创建:通过初始化操作对象(如 NSInvocationOperationNSBlockOperation 或自定义 NSOperation 子类的实例)创建操作。
  2. 添加到队列:将操作添加到 NSOperationQueue 中。
  3. 准备执行:当操作的所有依赖都完成,并且没有被取消时,操作进入 ready 状态,等待队列调度执行。
  4. 执行:队列调度操作,操作进入 executing 状态并开始执行其任务代码(如 main 方法或 block 中的代码)。
  5. 完成:操作执行完任务代码后,进入 finished 状态。如果在执行过程中被取消,则进入 cancelled 状态。

例如,在自定义 NSOperation 子类中,可以通过重写一些方法来更好地管理操作的生命周期。

@interface MyOperation : NSOperation
@property (nonatomic, assign, getter = isFinished) BOOL finished;
@property (nonatomic, assign, getter = isExecuting) BOOL executing;
@end

@implementation MyOperation
- (void)start {
    if (self.isCancelled) {
        self.finished = YES;
        return;
    }
    self.executing = YES;
    [self main];
}

- (void)main {
    if (!self.isCancelled) {
        // 具体任务代码
        NSLog(@"自定义操作执行");
        self.finished = YES;
    }
    self.executing = NO;
}

- (void)cancel {
    [super cancel];
    self.finished = YES;
    self.executing = NO;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return _executing;
}

- (BOOL)isFinished {
    return _finished;
}
@end

8. 取消操作与队列

在某些情况下,我们可能需要取消一个操作或整个队列的操作。

8.1 取消单个操作

通过调用操作对象的 cancel 方法可以取消单个操作。例如:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作执行");
}];
[queue addOperation:operation];
// 取消操作
[operation cancel];

在操作执行前调用 cancel 方法,操作将不会被执行。如果操作已经开始执行,操作内部需要检查 isCancelled 属性,以便在适当的时候停止执行。

8.2 取消队列中的所有操作

通过调用 NSOperationQueue 的 cancelAllOperations 方法可以取消队列中所有尚未开始执行的操作。对于已经开始执行的操作,同样需要操作内部自行处理取消逻辑。例如:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 1 执行");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"操作 2 执行");
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
// 取消队列中的所有操作
[queue cancelAllOperations];

9. NSOperationQueue 与 GCD 的关系

Grand Central Dispatch (GCD) 是 iOS 和 macOS 提供的另一种异步编程模型。NSOperationQueue 和 GCD 有一些相似之处,也有一些不同点。

9.1 相似点

  • 异步执行:两者都可以用于在后台线程执行任务,提高应用程序的响应性。
  • 队列管理:都有队列的概念,用于管理任务的执行顺序。

9.2 不同点

  • 灵活性:NSOperationQueue 更加灵活,它允许设置操作之间的依赖关系、优先级等,还可以通过自定义 NSOperation 子类来实现复杂的操作逻辑。而 GCD 更侧重于简单直接地将任务提交到队列中执行。
  • 面向对象与函数式:NSOperationQueue 是基于面向对象的方式,通过操作对象来管理任务;而 GCD 是基于函数式编程的方式,通过 block 来定义任务。
  • 系统资源管理:GCD 由系统自动管理线程的创建和销毁,而 NSOperationQueue 虽然也有一定的线程管理机制,但开发者可以更精细地控制操作的调度和执行。

例如,在简单的后台任务执行场景下,GCD 可能更简洁:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 后台任务代码
    NSLog(@"GCD 后台任务执行");
});

而在处理复杂的任务依赖和优先级场景下,NSOperationQueue 更具优势。

10. 实际应用场景

10.1 网络请求与数据处理

在应用程序中,经常需要进行网络请求并处理返回的数据。可以将网络请求操作和数据处理操作添加到 NSOperationQueue 中,并设置依赖关系。例如:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *networkOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 网络请求代码
    NSURL *url = [NSURL URLWithString:@"https://example.com/api"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 将数据传递给下一个操作
    NSBlockOperation *processingOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 数据处理代码
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"处理后的数据: %@", json);
    }];
    [processingOperation addDependency:networkOperation];
    [queue addOperation:processingOperation];
}];
[queue addOperation:networkOperation];

10.2 图片加载与处理

在图片浏览应用中,需要从网络或本地加载图片,并可能对图片进行裁剪、缩放等处理。可以将图片加载操作和图片处理操作添加到 NSOperationQueue 中,提高加载和处理效率。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadImageOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 图片加载代码,假设从本地加载
    NSString *path = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"jpg"];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    // 将图片传递给下一个操作
    NSBlockOperation *processImageOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 图片处理代码,例如裁剪
        CGRect cropRect = CGRectMake(0, 0, image.size.width / 2, image.size.height / 2);
        CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
        UIImage *croppedImage = [UIImage imageWithCGImage:croppedImageRef];
        CGImageRelease(croppedImageRef);
        NSLog(@"处理后的图片: %@", croppedImage);
    }];
    [processImageOperation addDependency:loadImageOperation];
    [queue addOperation:processImageOperation];
}];
[queue addOperation:loadImageOperation];

通过合理地使用 NSOperationQueue,可以有效地管理复杂的任务流程,提高应用程序的性能和响应性。在实际开发中,需要根据具体的需求和场景,灵活运用 NSOperationQueue 的各种特性。无论是简单的后台任务执行,还是复杂的任务依赖和优先级控制,NSOperationQueue 都提供了强大的功能来满足开发者的需求。同时,结合 GCD 等其他异步编程技术,可以进一步优化应用程序的多线程处理能力。在处理操作的生命周期、取消操作以及设置操作之间的关系时,需要仔细考虑应用程序的逻辑,以确保任务的正确执行和资源的有效管理。