一、NSOperation
1.简介
NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。NSOperation
和 NSOperationQueue
实现多线程的具体步骤:
- 先将需要执行的操作封装到一个 NSOperation 对象中
- 然后将 NSOperation 对象添加到
NSOperationQueue
中 - 系统会⾃动将
NSOperationQueue
中的 NSOperation 取出来 - 将取出的 NSOperation 封装的操作放到⼀条新线程中执⾏
2.NSOperation 子类
NSOperation 是个抽象类,并不具备封装操作的能力,必须使⽤它的子类
-
NSInvocationOperation
(Foundation框架) -
NSBlockOperation
(Foundation框架) - 自定义子类继承 NSOperation ,实现内部相应的⽅法
//创建操作对象,封装要执行的 test 任务
//NSInvocationOperation 封装操作
NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[operation start];
【注意】默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
//能够并发地执行一个或多个 block 对象,所有相关的 block 都执行完之后,操作才算完成
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行了一个新的操作,线程:%@", [NSThread currentThread]);
}];
//通过 addExecutionBlock 方法添加一个 block 操作
[operation addExecutionBlock:^() {
NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
}];
// 开始执行任务(这里还是同步执行)
[operation start];
【注意】只要NSBlockOperation
封装的操作数 > 1, 就会异步执行操作
3.执行操作
NSOperation 对象的 isConcurrent
方法默认返回 NO,表示操作与调用线程同步执行,start/cancel 开始或取消线程。监听操作的执行,可以通过调用 NSOperation 的 setCompletionBlock
方法来设置想做的事情
operation.completionBlock = ^() {...}; //或者
[operation setCompletionBlock:^() {...}];
二、自定义 NSOperation
【非常感谢】http://blog.csdn.net/q199109106q/article/details/8565923
1.简介
如果NSInvocationOperation
和 NSBlockOperation
对象不能满足需求, 你可以直接继承 NSOperation , 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的 NSOperation 。
- 定义非并发的 NSOperation 要简单许多,只需要重载
-(void)main
这个方法,在这个方法里面执行主任务,并正确地响应取消事件 - 对于并发 NSOperation , 你必须重写 NSOperation 的多个基本方法进行实现
2.非并发的 NSOperation:
比如叫做 DownloadOperation
,用来下载图片
1> 继承 NSOperation,重写 main 方法,执行主任务
DownloadOperation.h
#import <Foundation/Foundation.h>
@protocol DownloadOperationDelegate;
@interface DownloadOperation : NSOperation
// 图片的url路径
@property (nonatomic, copy) NSString *imageUrl;
// 代理
@property (nonatomic, retain) id<DownloadOperationDelegate> delegate;
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
@end
// 图片下载的协议
@protocol DownloadOperationDelegate <NSObject>
- (void)downloadFinishWithImage:(UIImage *)image;
@end
DownloadOperation.m
#import "DownloadOperation.h"
@implementation DownloadOperation
@synthesize delegate = _delegate;
@synthesize imageUrl = _imageUrl;
// 初始化
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {
if (self = [super init]) {
self.imageUrl = url;
self.delegate = delegate;
}
return self;
}
// 释放内存
- (void)dealloc {
[super dealloc];
[_delegate release];
[_imageUrl release];
}
// 执行主任务
- (void)main {
// 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
@autoreleasepool {
// ....
}
}
@end
2> 正确响应取消事件
operation 开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在 operation 执行之前。尽管 NSOperation 提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果 operation 直接终止, 可能无法回收所有已分配的内存或资源。因此 operation 对象需要检测取消事件,并优雅地退出执行
NSOperation 对象需要定期地调用 isCancelled
方法检测操作是否已经被取消,如果返回 YES (表示已取消),则立即退出执行。不管是自定义NSOperation 子类,还是使用系统提供的两个具体子类,都需要支持取消。 isCancelled
方法本身非常轻量,可以频繁地调用而不产生大的性能损失
以下地方可能需要调用isCancelled
:
- 在执行任何实际的工作之前
- 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
- 代码中相对比较容易中止操作的任何地方
DownloadOperation的main
方法实现如下:
- (void)main {
// 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
@autoreleasepool {
if (self.isCancelled) return;
// 获取图片数据
NSURL *url = [NSURL URLWithString:self.imageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:url];
if (self.isCancelled) {
url = nil;
imageData = nil;
return;
}
// 初始化图片
UIImage *image = [UIImage imageWithData:imageData];
if (self.isCancelled) {
image = nil;
return;
}
if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
// 把图片数据传回到主线程
[(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
}
}
}
三、NSOperationQueue
NSOperationQueue 的作⽤:
NSOperation 可以调⽤ start
⽅法来执⾏任务,但默认是同步执行的,如果将NSOperation 添加到 NSOperationQueue (操作队列)中,系统会自动异步执行 NSOperation 中的操作,添加操作到 NSOperationQueue 中,自动执行操作,自动开启线程。
//1. 创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2. 添加一个operation
// 第一种方法
[queue addOperation:operation];
// 第二种方法
[queue addOperationWithBlock:^() {
NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]);
}];
//3. 添加一组operation
[queue addOperations:operations waitUntilFinished:NO];
NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。
【注意】NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等。
四、内容补充
1、添加NSOperation的依赖对象:
当某个 NSOperation 对象依赖于其它 NSOperation 对象的完成时,就可以通过 addDependency
方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前 NSOperation 对象才会开始执行操作。另外,通过 removeDependency
方法来删除依赖对象。
[operation2 addDependency:operation1];
By the way, 优先级不能替代依赖关系,优先级只是对已经准备好的 operations 确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
2、 设置队列的最大并发操作数量:
setMaxConcurrentOperationCount:
方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于 GCD 中的串行 dispatch queue
// 每次只能执行一个操作
queue.maxConcurrentOperationCount = 1;
// 或者这样写
[queue setMaxConcurrentOperationCount:1];
3、 等待及暂停 operation :
如果需要在当前线程中处理 operation 完成后的结果,可以使用 NSOperation 的 waitUntilFinished
方法阻塞当前线程,等待 operation 完成。
// 会阻塞当前线程,等到某个operation执行完毕
[operation waitUntilFinished];
// 阻塞当前线程,等待queue的所有操作执行完毕
[queue waitUntilAllOperationsAreFinished];
暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。
// 暂停queue
[queue setSuspended:YES];
// 继续queue
[queue setSuspended:NO];
最后,NSOperationQueue属于多线程并发处理部分,它主要用来提供一个可添加的操作队列,将一系列操作添加到队列中,然后根据操作的优先级和内部操作依赖决定操作执行的顺序。高优先级的操作先于低优先级执行。一个操作所依赖的操作全部执行完毕后才能执行。