深入学习Objective-C中NSOperationQueue的语法与使用
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 操作的生命周期
- 创建:通过初始化操作对象(如
NSInvocationOperation
、NSBlockOperation
或自定义NSOperation
子类的实例)创建操作。 - 添加到队列:将操作添加到 NSOperationQueue 中。
- 准备执行:当操作的所有依赖都完成,并且没有被取消时,操作进入 ready 状态,等待队列调度执行。
- 执行:队列调度操作,操作进入 executing 状态并开始执行其任务代码(如
main
方法或 block 中的代码)。 - 完成:操作执行完任务代码后,进入 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 等其他异步编程技术,可以进一步优化应用程序的多线程处理能力。在处理操作的生命周期、取消操作以及设置操作之间的关系时,需要仔细考虑应用程序的逻辑,以确保任务的正确执行和资源的有效管理。