iOS开发基础37-多线程之NSOperation
NSOperation 与 NSOperationQueue 深度解析:操作封装、队列管理、依赖关系与自定义并发 Operation
本文系统梳理 NSOperation 多线程编程:NSOperation 抽象类与三种子类、NSOperationQueue 队列管理(最大并发数/取消/暂停/恢复)、操作依赖、完成回调、优先级与服务质量、自定义非并发与并发 Operation,以及下载合成图片综合示例。
一、基本概念
1. NSOperation 的作用
NSOperation 与 NSOperationQueue 搭配实现多线程:
- 将任务封装到
NSOperation对象中; - 将
NSOperation添加到NSOperationQueue; - 系统自动取出 operation 并放到子线程异步执行。
NSOperation 是基于 GCD 的面向对象封装,比 GCD 提供更丰富的任务控制:依赖关系、取消、最大并发数、KVO 状态观察、优先级。
2. NSOperation 的子类
NSOperation 是抽象类,不能直接使用,必须使用其子类:
| 子类 | 说明 | 状态 |
|---|---|---|
NSInvocationOperation |
基于 target/selector 封装操作 | iOS 8.0 起废弃,不推荐 |
NSBlockOperation |
基于 block 封装操作,支持多 block 并发 | 推荐 |
| 自定义子类 | 继承 NSOperation,重写 main(非并发)或 start(并发) |
复杂场景推荐 |
二、三种子类的使用
1. NSInvocationOperation(已废弃)
// 创建(基于 target/selector)
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(doWork)
object:nil];
// 手动 start(在当前线程同步执行)
[op start];
NSInvocationOperation在 iOS 8.0 已废弃,推荐使用NSBlockOperation或自定义子类替代。默认调用start在当前线程同步执行,只有添加到NSOperationQueue才会异步执行。
2. NSBlockOperation
// 创建(第一个 block)
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"第一个 block: %@", [NSThread currentThread]);
}];
// 添加更多 block(这些 block 会并发执行,可能在不同线程)
[op addExecutionBlock:^{
NSLog(@"附加 block 1: %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"附加 block 2: %@", [NSThread currentThread]);
}];
// 手动 start
[op start];
执行线程说明:
NSBlockOperation的第一个 block(创建时传入)在调用start的线程执行;通过addExecutionBlock:添加的 block 会并发执行(可能在不同线程)。是否异步执行取决于是否添加到NSOperationQueue——添加到队列后所有 block 都在子线程异步执行,与 block 数量无关。
3. 自定义子类(非并发,重写 main)
@interface CustomOperation : NSOperation
@end
@implementation CustomOperation
- (void)main {
// 非并发 Operation 在子线程执行,需要自己的 autoreleasepool
@autoreleasepool {
if (self.isCancelled) return; // 开始前检查取消状态
// 执行耗时操作
NSLog(@"自定义操作: %@", [NSThread currentThread]);
if (self.isCancelled) return; // 耗时操作后再次检查
}
}
@end
使用:
CustomOperation *op = [[CustomOperation alloc] init];
[op start]; // 或添加到队列异步执行
非并发 Operation 只需重写
main,队列自动管理执行线程和状态(isReady/isExecuting/isFinished)。在main中应定期检查isCancelled,以便及时响应取消。
三、NSOperationQueue
1. 基本使用
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加 operation
[queue addOperation:op1];
[queue addOperation:op2];
// 便捷方法:直接添加 block(自动创建 NSBlockOperation)
[queue addOperationWithBlock:^{
NSLog(@"直接添加的 block 任务");
}];
2. 主队列
// 获取主队列(任务在主线程执行,用于更新 UI)
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
self.imageView.image = image; // 主线程更新 UI
}];
3. 最大并发数
// 设置最大并发数(同时执行的 operation 数)
queue.maxConcurrentOperationCount = 3;
// maxConcurrentOperationCount = 1 → 串行队列(任务顺序执行)
// maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount(默认值,-1)→ 系统根据资源自动决定最大并发数
maxConcurrentOperationCount控制同时执行的 operation 数量,不是线程数量(一个 operation 可能内部开多线程)。设为 1 时队列变为串行队列。
4. 取消、暂停、恢复
// 取消所有操作(设置所有 operation 的 isCancelled=YES)
[queue cancelAllOperations];
// 暂停队列(不取出新的 operation,正在执行的不受影响)
queue.suspended = YES;
// 恢复队列
queue.suspended = NO;
// 阻塞当前线程,直到队列中所有操作完成(慎用,会阻塞当前线程)
[queue waitUntilAllOperationsAreFinished];
重要:
cancelAllOperations和suspended只影响尚未开始执行的 operation,正在执行的 operation 不会被强制停止。cancelAllOperations只是设置isCancelled=YES,需要 operation 在main中主动检查并停止。
四、高级用法
1. 操作依赖
operation 之间可设置依赖关系,被依赖的 operation 完成后才执行当前 operation:
// op3 依赖 op1 和 op2(op1、op2 都完成后 op3 才执行)
[op3 addDependency:op1];
[op3 addDependency:op2];
// 移除依赖
[op3 removeDependency:op1];
- 依赖可以跨队列设置(不同 queue 的 operation 也可以建立依赖)。
- 绝对不能循环依赖(A 依赖 B,B 依赖 A),会导致死锁,两个 operation 都永远不会执行。
2. 完成回调(completionBlock)
op.completionBlock = ^{
NSLog(@"操作完成");
// completionBlock 执行线程不确定,更新 UI 需回主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.label.text = @"完成";
}];
};
completionBlock在 operation 完成(isFinished=YES)后执行,执行线程不确定(可能在任意线程),如需更新 UI 必须切回主线程。
3. 优先级与服务质量
// 队列优先级(NSOperationQueuePriority 枚举)
op.queuePriority = NSOperationQueuePriorityHigh;
// NSOperationQueuePriorityVeryLow / Low / Normal / High / VeryHigh
// 服务质量(iOS 8+,替代部分优先级场景)
op.qualityOfService = NSQualityOfServiceUserInitiated;
queuePriority决定同一队列中 operation 的执行顺序(高优先级先取出),不决定绝对执行时间。qualityOfService影响系统分配的 CPU 资源和优先级。
4. 操作状态属性
BOOL ready = op.isReady; // 是否准备好执行(依赖都满足时为 YES)
BOOL executing = op.isExecuting; // 是否正在执行
BOOL finished = op.isFinished; // 是否执行完成
BOOL cancelled = op.isCancelled; // 是否被取消
这些属性支持 KVO,可以观察状态变化。自定义并发 Operation 时需手动触发这些属性的 KVO 通知。
五、自定义并发 Operation
非并发 Operation 重写 main 即可,队列自动管理线程和状态。并发 Operation 需要自己管理执行线程和状态,重写以下方法:
@interface ConcurrentOperation : NSOperation
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@end
@implementation ConcurrentOperation
// 标记为并发 Operation
- (BOOL)isConcurrent {
return YES;
}
// 重写 start(不调用 super,自己管理执行)
- (void)start {
if (self.isCancelled) {
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// 手动触发 KVO
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
// 在新线程异步执行任务
[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];
}
- (void)doWork {
@autoreleasepool {
if (self.isCancelled) {
[self completeOperation];
return;
}
// 执行异步任务(如网络请求)
NSLog(@"并发操作执行中");
[self completeOperation];
}
}
// 完成操作,手动更新状态
- (void)completeOperation {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];
}
@end
并发 Operation 关键点:
isConcurrent返回 YES;- 重写
start(不调用[super start]),自己管理执行线程;- 手动管理
isExecuting/isFinished状态并触发 KVO(willChangeValueForKey:/didChangeValueForKey:);- 任务完成后必须正确设置
isFinished=YES,否则队列认为 operation 未完成,不会释放。
六、综合示例:下载并合成图片
异步下载两张图片,都完成后合成为一张,最后回主线程更新 UI:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 下载图片 1
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://example.com/image1.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
image1 = [UIImage imageWithData:data];
}];
// 下载图片 2
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://example.com/image2.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
image2 = [UIImage imageWithData:data];
}];
// 合成图片(依赖 op1 和 op2)
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 回主线程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = newImage;
}];
}];
// 设置依赖
[op3 addDependency:op1];
[op3 addDependency:op2];
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
}
七、Swift 版本对照
基本使用
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
// 添加 block 操作
queue.addOperation {
print("任务: \(Thread.current)")
}
// BlockOperation(多 block 并发)
let op = BlockOperation {
print("第一个 block")
}
op.addExecutionBlock { print("附加 block 1") }
op.addExecutionBlock { print("附加 block 2") }
queue.addOperation(op)
// 主队列(更新 UI)
OperationQueue.main.addOperation {
self.imageView.image = image
}
自定义非并发 Operation
class CustomOperation: Operation {
override func main() {
guard !isCancelled else { return }
// 执行操作
print("自定义操作: \(Thread.current)")
guard !isCancelled else { return }
}
}
依赖与完成回调
let op1 = BlockOperation { print("下载图片1") }
let op2 = BlockOperation { print("下载图片2") }
let op3 = BlockOperation { print("合成图片") }
op3.addDependency(op1)
op3.addDependency(op2)
op3.completionBlock = {
OperationQueue.main.addOperation {
print("回主线程更新 UI")
}
}
queue.addOperations([op1, op2, op3], waitUntilFinished: false)
取消、暂停、恢复
queue.cancelAllOperations()
queue.isSuspended = true // 暂停
queue.isSuspended = false // 恢复
queue.waitUntilAllOperationsAreFinished() // 阻塞等待
优先级与服务质量
op.queuePriority = .high
op.qualityOfService = .userInitiated
自定义并发 Operation
class ConcurrentOperation: Operation {
private var _executing = false
private var _finished = false
override var isAsynchronous: Bool { true }
override var isExecuting: Bool { _executing }
override var isFinished: Bool { _finished }
override func start() {
if isCancelled {
finish()
return
}
willChangeValue(forKey: "isExecuting")
_executing = true
didChangeValue(forKey: "isExecuting")
// 异步执行任务
DispatchQueue.global().async {
print("并发操作")
self.finish()
}
}
private func finish() {
willChangeValue(forKey: "isExecuting")
willChangeValue(forKey: "isFinished")
_executing = false
_finished = true
didChangeValue(forKey: "isFinished")
didChangeValue(forKey: "isExecuting")
}
}
八、总结
- NSOperation:抽象类,三种子类——
NSInvocationOperation(已废弃)、NSBlockOperation(推荐,支持多 block 并发)、自定义子类(重写main非并发 / 重写start并发)。 - NSOperationQueue:管理 operation 并异步执行;
maxConcurrentOperationCount控制最大并发数(=1 为串行,默认系统决定);主队列[NSOperationQueue mainQueue]任务在主线程执行。 - 取消/暂停/恢复:
cancelAllOperations设置 isCancelled(正在执行的需主动检查);suspended暂停取出新任务;都不影响正在执行的任务。 - 操作依赖:
addDependency:设置依赖(可跨队列),removeDependency:移除;禁止循环依赖。 - 完成回调:
completionBlock在操作完成后执行,线程不确定,更新 UI 需回主线程。 - 优先级:
queuePriority决定执行顺序,qualityOfService影响系统资源分配。 - 状态属性:
isReady/isExecuting/isFinished/isCancelled,支持 KVO。 - 自定义并发 Operation:重写
start(不调用 super)、isConcurrent返回 YES,手动管理isExecuting/isFinished状态并触发 KVO,完成后必须设置 isFinished=YES。 - 与 GCD 对比:NSOperation 是 GCD 的面向对象封装,提供依赖、取消、最大并发数、KVO 状态等高级控制;GCD 更轻量简洁,适合简单任务;复杂任务调度推荐 NSOperation。

浙公网安备 33010602011771号