下面是根据官方文档及个人理解写的,并使用NSOperation简单实现了一个SDWebImage库类似功能的MultiImageDownloadDome有不足或错误之处欢迎指正谢谢
NSOperation 简单介绍
NSOperation 是居于objective-c对GCD封装居于面向对象的,线程执行方式;NSOperation配合NSOperationQueue也能实现多线程编程;
NSOperatio也有两个核心概念操作,队列,在之前的GCD一章中介绍过GCD的核心概念是任务,队列,这两个其实是一样的。详细讲解请查看官方文档
如何使用?
使用NSOperation进行开发,有如下几部:
- 先将需要执行的操作封装到一个NSOperation对象中。
- 然后将NSOperation对象添加到NSOperationQueue中
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出的NSOperation封装的操作放到一条新线程中执行(底层居于GCD的会自己创建线程执行任务,不需要程序员操心)
NSOperation子类
NSOperation是一个抽象类,本身并不具备封装操作的能力,必须使用其子类。使用NSOperation的子类有两种方式,使用apple提供的现有的子类NSInvocationOperation
、NSBlockOperation
以及自定义子类继承NSOperation实现内部相应的方法。现简单说明系统提供的子类的使用
NSInvocationOperation
使用initWithTarget:selector:object:
不配合NSOperationQueue
使用的话是在当前线程中串行执行,不会开启新线程的
NSInvocationOperation *op1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"abc"];
[op1 start];
-(void)task:(NSString *)param{
NSLog(@"%@--%@",param,[NSThread currentThread]);
}
NSBlockOperation
使用比较简单可直接在block中实现任务,不配合Queues使用的话也是在当前线程串行执行的。
注意:当使用NSBlockOperation的addExecutionBlock方法追加任务时,一个operation的任务大于1个任务时那么会开启子线程并发执行,不过也不一定是子线程,有可能是主线程这是不确定性的
NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"4 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"5 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"6 -- %@",[NSThread currentThread]);
}];
[op1 start];
[op2 start];
[op3 start];
NSOperationQueue的基本使用
之前的GCD一章中说过GCD有两种队列类型串行队列(create创建、主队列)、并行队列(create创建、全局队列(有四种)),NSOperationQueue就没那么复杂了总共就两个,一个主队列、非主队列
- 主队列
NSOperationQueue mainQueue
和GCD中的主队列一样 - 非主队列
[[NSOperationQueue alloc]init]
非常特殊同时具备和串并发
行功能
默认情况下是并发队列
,何时是串行呢,在后面会有讲解;先看简单实用示例:
-(void)invocationOperationQueue{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *op1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"abc"];
NSInvocationOperation *op2=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"abc2"];
NSInvocationOperation *op3=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"abc3"];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
-(void)blockOperationQueue{
NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"4 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"5 -- %@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"6 -- %@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
在示例中可见并没有和之前实例中调用operation的start方法启动,这是因为queue的addOperation
方法内部调用了start方法;
NSOperationQueue还有一个简单方法添加任务,如下
//内部创建了operation
[queue addOperationWithBlock:^{
NSLog(@"7 -- %@",[NSThread currentThread]);
}];
NSOperationQueue的其他用法
NSOperationQueue默认是并行队列,如何创建串行的呢?
通过maxConcurrentOperationCount
设置最大并发数,同一时间最多有多个任务可以执行
-
maxConcurrentOperationCount>1 那么就是并发队列
-
maxConcurrentOperationCount=1 那么就是串行队列
-
maxConcurrentOperationCount=-1特殊意义 最大值表示不限制
-
suspended
是用来暂停任务的,队列里面有很多任务,假如有20个任务 我执行完3个任务,如果后面的任务不想执行了,使用该属性可暂停。 -
cancelAllOperations
取消执行任务。改方法内部调用当前队列里面所有的cancel方法
注意:suspended暂停任务是暂停还未执行的任务,执行的任务会继续执行,暂停还克恢复;cancelAllOperations取消任务,也是取消为执行的任务,取消后将不能再次恢复任务了
//串行队列
-(void)serialOperationWithQueue{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=1;
NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 -- %@",[NSThread currentThread]);
}];
NSBlockOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3 -- %@",[NSThread currentThread]);
}];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
NSOperation操作依赖和监听
在GCD中通过栅栏函数,和队列组,可以实现任务的执行顺序。NSOperation如何实现呢,这涉及到NSOperation的操作依赖了addDependency
,NSOperation的依赖比较强大的地方不光是同一个队列中的操作克依赖,其他队列中的操作也可依赖,还可以依赖多个
注意:不要循环依赖,不然会造成死锁
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 --%@",[NSThread currentThread]);
}];
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 --%@",[NSThread currentThread]);
}];
NSBlockOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3 --%@",[NSThread currentThread]);
}];
NSBlockOperation *op4=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4 --%@",[NSThread currentThread]);
}];
[op2 addDependency:op4];
[op3 addDependency:op2];
[op1 addDependency:op3];
/*
[op2 addDependency:op4];
[op2 addDependency:op3];
[op2 addDependency:op1];
*/
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
/*
NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue2 addOperation:op3];
[queue2 addOperation:op4];
*/
监听
例如,在下一个很大的文件如3g的文件,不可能一直盯着,可能需要在下载完成后,通过某些方式通知到你,这种需求就涉及到操作的监听监听操作的执行状态,实现很简单.NSOperaton提供了一个completionBlock,当操作完成是回调用completionBlock通知任务完成
自定义NSOperation
使用自定义NSOperation的优点
- 有利于任务代码的隐蔽性
- 有利于代码的复用性
自定义NSOperation,需要实现main方法用于封装任务,如果只是串行执行的自定义NSOperation比较简单只要在main中判断取消操作就好,如果是并发则比较复杂需要额外添加一些代码来控制详细讲解请看官方并发编程指南
如下是官方非并发简单code
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
下表是官方提供自定义NSOperation,实现并发需要自己实现相关代码的基本参数和方法
下面是简单实现自定义NSOperation并发的功能示例
@interface TDOperation : NSOperation
{
BOOL executing;//标识任务是否正在执行
BOOL finished;//标识任务是否执行完毕
}
@property(nonatomic,assign)NSInteger taskName;
@end
@implementation TDOperation
- (instancetype)init
{
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
-(void)start{
//在启动任务之前总是检查是否取消。
if ([self isCancelled]) {
//如果操作被取消,必须将操作移动到完成状态。
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
//如果操作没有被取消,则开始执行任务。
[self willChangeValueForKey:@"isExecuting"];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self main];
}
-(void)main{
if (self.isCancelled) {
return;
}
@try {
//执行任务
for (NSInteger i=0; i<10000; i++) {
NSLog(@"%zd --- %zd -- %@",self.taskName,i,[NSThread currentThread]);
}
//执行完毕后告知执行完毕
[self completeOperation];
} @catch (NSException *exception) {
//捕获异常后不要抛出新的
}
}
-(void)completeOperation
{
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
//标识是否是并发
-(BOOL)isConcurrent{
return YES;
}
-(BOOL)isExecuting{
return executing;
}
-(BOOL)isFinished{
return finished;
}
@end
NSOperation实现线程间的通信
如有这样一个需求,下载两张大图片,下载完成后再合并的功能;前面将GCD的时候可以通过栅栏函数和组队列来实现,NSOperation改如何实现。
例如:
-(void)combieImage{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
__weak typeof(self) weakSelf = self;
__block UIImage *img1;
__block UIImage *img2;
NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:^{
NSString *imgstr=@"https://t7.baidu.com/it/u=2511982910,2454873241&fm=193&f=GIF";
img1=[weakSelf downloadImage:imgstr];
}];
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSString *imgstr=@"https://t7.baidu.com/it/u=139984722,3523412696&fm=193&f=GIF";
img2=[weakSelf downloadImage:imgstr];
}];
NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
CGFloat hight = 320.0*9.0/16.0;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(320.0, hight), YES, [UIScreen mainScreen].scale);
[img2 drawInRect:CGRectMake(0, 0, 160, hight)];
[img2 drawInRect:CGRectMake(0, 0, 160, hight) blendMode:kCGBlendModeHue alpha:0.5];
[img1 drawInRect:CGRectMake(160, 0, 160, hight)];
UIImage *comImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
weakSelf.imageView.image=comImg;
}];
}];
[combie addDependency:op];
[combie addDependency:op2];
[queue addOperation:op];
[queue addOperation:op2];
[queue addOperation:combie];
}
-(UIImage *)downloadImage:(NSString *)imgUrlStr
{
NSURL *url = [NSURL URLWithString:imgUrlStr];
NSData *data=[NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
return image;
}
使用NSOperation简单实现SDWebImage类似功能MultiImageDownloadDome