iOS开发基础37-多线程之NSOperation

NSOperation 与 NSOperationQueue 深度解析:操作封装、队列管理、依赖关系与自定义并发 Operation

本文系统梳理 NSOperation 多线程编程:NSOperation 抽象类与三种子类、NSOperationQueue 队列管理(最大并发数/取消/暂停/恢复)、操作依赖、完成回调、优先级与服务质量、自定义非并发与并发 Operation,以及下载合成图片综合示例。


一、基本概念

1. NSOperation 的作用

NSOperationNSOperationQueue 搭配实现多线程:

  1. 将任务封装到 NSOperation 对象中;
  2. NSOperation 添加到 NSOperationQueue
  3. 系统自动取出 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];

重要cancelAllOperationssuspended 只影响尚未开始执行的 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 关键点:

  1. isConcurrent 返回 YES;
  2. 重写 start(不调用 [super start]),自己管理执行线程;
  3. 手动管理 isExecuting/isFinished 状态并触发 KVO(willChangeValueForKey:/didChangeValueForKey:);
  4. 任务完成后必须正确设置 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。

posted @ 2015-08-20 15:14  Mr.陳  阅读(592)  评论(0)    收藏  举报