NSOperation 简介

0.065字数 3694阅读 503

一、简介

除了,NSThread和GCD实现多线程,配合使用NSOperation和NSOperationQueue也能实现多线程编程

NSOperation和NSOperationQueue实现多线程的具体步骤

1、先将需要执行的操作封装到一个NSOperation的子类对象中

实际上,NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

2、然后将NSOperation对象添加到NSOperationQueue中

3、系统会自动将NSOperationQueue中的NSOperation取出来

4、将取出的NSOperation封装的操作放到一条新线程中执行

二、NSOperation

如上所示:要实现多线程,必须要将执行的操作封装到NSOperation的子类对象中,那么NSOperation的子类有哪些?

1、使用NSOperation子类的方式有3种

NSInvocationOperation

NSBlockOperation

自定义子类继承NSOperation,实现NSOperation的某些方法

NSInvocationOperation

创建NSInvocationOperation对象

-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)arg;

NSInvocationOperation将操作封装成SEL,只有将NSInvocationOperation添加到NSOperationQueue中,才会异步执行操作。

- (void)test {

// 1.将操作封装到Operation中

NSInvocationOperation*op1 = [[NSInvocationOperationalloc] initWithTarget:selfselector:@selector(demo) object:nil];

// 2.执行封装的操作

// 如果直接执行NSInvocationOperation中的操作, 那么默认会在主线程中执行

//[op1 start];

// 创建队列

NSOperationQueue*queue = [[NSOperationQueuealloc] init];

//将任务加入到队列 任务会自动开启

[queue addOperation:op1];

}

NSBlockOperation

创建NSBlockOperation 对象

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

NSBlockOperation将任务封装成一个block,这种方式看起来更方便。当然,你还可以调用-addExecutionBlock:方法,添加另一个任务。

注意点:只要NSBlockOperation封装的操作数 >1,直接调用start方法,就会异步执行操作,但是默认的初始化的block任务是在主线程中执行。

- (void)test {

NSBlockOperation*blockOperation = [NSBlockOperationblockOperationWithBlock:^{

NSLog(@"开始执行任务");

}];

[blockOperation addExecutionBlock:^{

NSLog(@"开始下一个执行任务");

}];

//如果不将NSBlockOperation添加到队列中,而是直接调用start方法,那么默认初始化时的任务执行在主线程中,而通过addExecutionBlock方法添加的任务都执行在子线程中。

//[blockOperation start];

// 创建队列

NSOperationQueue*queue = [[NSOperationQueuealloc] init];

//将任务加入到队列 任务会自动开启

[queue addOperation: blockOperation];

}

自定义 NSOperation

首先需要继承NSOperation,然后实现某些方法。

我们有两种方法来实现自定义operation,一种是通过重写main方法,一种是通过重写start方法,前者实现起来更简单,我们不需要管理一些属性状态,例如isExecuting或者isFinished等。当main方法执行结束的时候,这个operation就结束了。另外一种是重写start方法,但是这种方法需要你管理各种属性的状态,虽然麻烦,但是相对灵活。如果你同时实现start和main方法,则优先使用start方法。因为第一种方法比较简单,我们这里以第二种方法来自定义operation。

首先,父类提供了一系列的getter方法,用以获知任务的状态。

@property (readonly, getter=isReady) BOOL ready

此属性表明NSOperation是否已经准备好开始执行任务了,如果返回false,NSOperation的之后的方法都不会执行。通常情况下,自定义operation,我们可以利用它来让调用者必须满足一定的条件才能执行任务。

@property (readonly, getter=isExecuting) BOOL executing

是否正在执行。

@property (readonly, getter=isFinished) BOOL finished

任务是否完成

@property (readonly, getter=isCancelled) BOOL cancelled

任务是否取消

@property (readonly, getter=isConcurrent) BOOL concurrent

表示是否异步执行任务

自定义operation如下

MyOperation.h

@interfaceMyOperation:NSOperation

+ (instancetype)crateOperationWithMoney:(int)money;

@end

MyOperation.m

#import"MyOperation.h"

#import

@interfaceMyOperation()

@property(nonatomic,assign)intmoney;

@property(nonatomic,assign)BOOLcancelled;

@property(nonatomic,assign)BOOLexecuting;

@property(nonatomic,assign)BOOLfinished;

@property(nonatomic,assign)BOOLconcurrent;

@property(nonatomic,assign)BOOLasynchronous;

@end

@implementationMyOperation

@synthesizecancelled;

@synthesizeexecuting;

@synthesizefinished;

@synthesizeconcurrent;

@synthesizeasynchronous;

+ (instancetype)crateOperationWithMoney:(int)money {

MyOperation *operation = [[MyOperation alloc] init];

operation.money = money;

returnoperation;

}

- (BOOL)isReady {

//只有当金额大于等于100的时候才执行任务

return_money >=100;

}

- (void)start {

//判断任务状态

if(self.isExecuting ||self.isCancelled ||self.isFinished) {

return;

}

NSLog(@"%@--%@开始花钱",self.name,[NSThreadcurrentThread]);

self.executing =YES;

while(self.money >0){

self.money--;

NSLog(@"%@花钱了%d",self.name,self.money);

}

//执行完毕

self.finished =YES;

self.executing =NO;

}

- (BOOL)isExecuting {

returnself.executing;

}

- (void)cancel {

NSLog(@"%@%@取消",[NSThreadcurrentThread],self.name);

self.finished =YES;

self.cancelled =YES;

self.executing =NO;

}

- (BOOL)isCancelled {

returnself.cancelled;

}

- (BOOL)isFinished {

returnself.finished;

}

- (BOOL)isConcurrent {

returnself.concurrent;

}

- (BOOL)isAsynchronous {

returnself.asynchronous;

}

- (void)dealloc {

NSLog(@"%@dealloc",self.name);

}

@end

这仅仅是一个简单的实例,真正的项目中还需要打磨,如果没有特殊的要求,还是使用系统提供的比较好。

添加依赖

目的 -> NSOperation之间可以设置依赖来保证执行顺序

例如:一定要让操作A执行完后,才能执行操作B,可以这么写

[operationB addDependency:operationA];

例如我们通过不同的线程下载图片 当都下载完成以后在合成图片

#import"ViewController.h"

@interfaceViewController()

@property(weak,nonatomic)IBOutletUIImageView*imageView;

@end

@implementationViewController

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

NSOperationQueue*queue = [[NSOperationQueuealloc] init];

NSOperationQueue*queue2 = [[NSOperationQueuealloc] init];

__blockUIImage*image1 =nil;

__blockUIImage*image2 =nil;

// 1.开启一个线程下载第一张图片

NSOperation*op1 = [NSBlockOperationblockOperationWithBlock:^{

NSURL*url = [NSURLURLWithString:@"http://cdn.cocimg.com/assets/images/logo.png?v=201510272"];

NSData*data = [NSDatadataWithContentsOfURL:url];

// 2.生成下载好的图片

UIImage*image = [UIImageimageWithData:data];

image1 = image;

}];

// 2.开启一个线程下载第二长图片

NSOperation*op2 = [NSBlockOperationblockOperationWithBlock:^{

NSURL*url = [NSURLURLWithString:@"https://www.baidu.com/img/bd_logo1.png"];

NSData*data = [NSDatadataWithContentsOfURL:url];

// 2.生成下载好的图片

UIImage*image = [UIImageimageWithData:data];

image2 = image;

}];

// 3.开启一个线程合成图片

NSOperation*op3 = [NSBlockOperationblockOperationWithBlock:^{

UIGraphicsBeginImageContext(CGSizeMake(200,200));

[image1 drawInRect:CGRectMake(0,0,100,200)];

[image2 drawInRect:CGRectMake(100,0,100,200)];

UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

// 4.回到主线程更新UI

[[NSOperationQueuemainQueue] addOperationWithBlock:^{

NSLog(@"回到主线程更新UI");

self.imageView.image = newImage;

}];

}];

// 监听任务是否执行完毕

op1.completionBlock = ^{

NSLog(@"第一张图片下载完毕");

};

op2.completionBlock = ^{

NSLog(@"第二张图片下载完毕");

};

// 添加依赖

// 只要添加了依赖, 那么就会等依赖的任务执行完毕, 才会执行当前任务

// 注意:

// 1.添加依赖, 不能添加循环依赖

// 2.NSOperation可以跨队列添加依赖

[op3 addDependency:op1];

[op3 addDependency:op2];

// 将任务添加到队列中

[queue addOperation:op1];

[queue addOperation:op2];

[queue2 addOperation:op3];

}

@end

三、NSOperationQueue

NSOperation可以调用start方法来执行操作,但是默认是同步执行的。将NSOperation添加到NSOperationQueue中,系统会自动异步调用NSOperation的start方法或者main方法来执行任务。

NSOperationQueue提供了三种添加任务的方法:

- (void)addOperation:(NSOperation *)op将NSOperation对象添加到队列中

- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait将NSOperation对象数组添加到队列中,wait表示是否阻塞当前队列,YES表明以后添加的任务只能在ops中的所有任务执行完成以后才会执行,NO表示不阻塞当前队列

- (void)addOperationWithBlock:(void (^)(void))block将一个block添加到任务中。

这里有几点需要注意的,添加的任务一定不能是正在执行的或者已经完成的,可通过executing和finished来判断。不能重复添加一个operation到同一个队列中,否则会报NSInvalidArgumentException异常。

再来看看NSOperationQueue的属性:

operations当前正在执行或者等待执行的任务的集合,如果一个任务执行完毕,会从中删除。

operationCount当前队列中的任务数(正在执行或者等待执行)

maxConcurrentOperationCount最大并发数,默认是-1,即无限大,如果设置为1,那么当前队列即为串行队列,但不能设置成0,如果设置成0,那么当前的所有任务都不能执行。

name队列的名字

当然,NSOperationQueue也提供了取消操作,

- (void)cancelAllOperations,此方法会取消所有未执行的操作。内部实现是分别调用任务的cancel方法。但是已经执行的操作将会继续执行。另外此方法并不会将取消的操作从队列中移除。但是实际却移除了,这是因为NSInvocationOperation和NSBlockOperation内部自己管理了finish状态,在cancel方法中会将当前任务的finish设置为YES。

- (void)waitUntilAllOperationsAreFinished阻塞当前任务队列。




NSOperation的作用

配合使用NSOperation和NSOperationQueue也能实现多线程编程

NSOperation和NSOperationQueue实现多线程的具体步骤

先将需要执行的操作封装到一个NSOperation对象中

然后将NSOperation对象添加到NSOperationQueue中

系统会自动将NSOperationQueue中的NSOperation取出来

将取出的NSOperation封装的操作放到一条新线程中执行

NSOperation的子类

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

使用NSOperation子类的方式有3种

NSInvocationOperation

NSBlockOperation

自定义子类继承NSOperation,实现内部相应的方法

创建NSInvocationOperation对象

-(id)initWithTarget:(id)targetselector:(SEL)selobject:(id)arg;

调用start方法开始执行操作

-(void)start;

一旦执行操作,就会调用target的sel方法

注意

默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作

只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

创建NSBlockOperation对象

+(id)blockOperationWithBlock:(void(^)(void))block;

通过addExecutionBlock:方法添加更多的操作

-(void)addExecutionBlock:(void(^)(void))block;

注意:只要NSBlockOperation封装的操作数 >1,就会异步执行操作

NSOperationQueue的作用

NSOperation可以调用start方法来执行任务,但默认是同步执行的

如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

添加操作到NSOperationQueue中

-(void)addOperation:(NSOperation*)op;

-(void)addOperationWithBlock:(void(^)(void))block;

什么是并发数

同时执行的任务数

比如,同时开3个线程执行3个任务,并发数就是3

最大并发数的相关方法

-(NSInteger)maxConcurrentOperationCount;

-(void)setMaxConcurrentOperationCount:(NSInteger)cat;

取消队列的所有操作

-(void)cancelAllOperations;

提示:也可以调用NSOperation的-(void)cancel方法取消单个操作

暂停和恢复队列

-(void)setSuspended:(BOOL)b;// YES代表暂停队列,NO代表恢复队列

-(BOOL)isSuspended;

Operation之间可以设置依赖来保证执行顺序

比如一定要让操作A执行完后,才能执行操作B,可以这么写

[operationBaddDependency:operationA];// 操作B依赖于操作A

可以在不同queue的NSOperation之间创建依赖关系

可以监听一个操作的执行完毕

-(void(^)(void))completionBlock;

-(void)setCompletionBlock:(void(^)(void))block;

自定义NSOperation的步骤很简单

重写-(void)main方法,在里面实现想执行的任务

重写-(void)main方法的注意点

自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)

经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

一个操作需要取消时,必须等这个操作的任务执行完毕,或者在这个操作的内容不断的判断是否有取消操作。

总结:

回主线刷新UI

[[NSOperationQueue mainQueue] addOprationWithBblock:^{回主线程刷新UI}];

------------------------------------------------------------

操作:NSOperation、NSInvocationOperation、NSBlockOpearation。

队列:NSOperationQueue

当使用NSInvocationOperation时

操作加入队列时:(注意:当前线程决定currentQueue在主队列还是在子队列)

1、不需要调用start方法,如果调用start方法会崩溃。

2、在主队列时(和当前线程无关)在主线程执行

3、在自定义队列时=> [[NSOperationQueue alloc]init](和当前线程无关)在子线程中执行

不加入队列时:

1、需要调用start方法,在当前线程中执行(主=主,子=子)

所以:队列决定了线程在主线程还是子线程中执行

所在队列执行线程.png

当是用NSBlockOperation时

不加入队列,:需要调用start方法

直接用NSBlockOperation封装的任务会在当前线程中执行(主=主,子=子)。

当使用addExecutionBlock添加任务后,也就是当任务的个数大于1,那么之后的任务会在子线程中执行,NSBlockOperation封装的任务还是会在主线程中执行。

加入队列:不需要调用start方法

会在子线程中执行任务。

NSINvocationOperation和NSBlockOpertaion和自定义的NSOperation都可以同时加入到同一个队列中,不需要调用start方法,并且都是在子线程中执行。

自定义NSOPeration需要将操作封装到自定的这个对象中,通过重写mian方法来实现封装任务。

异步串行执行任务的方式

NSOpration实现异步串行执行的方式:

1、设置最大并发数为1。

2、不设置并发数时,设置任务之间的依赖。

而GCD实现异步串行执行的方式:

1、异步+自定义串行队列(非主队列)

2、dispatch_barrier_async栅栏函数

NSOperation比GCD的好处

1、能通过KVO键值观察者来实时监控operation的状态(是否执行,是否取消),而GCD无法通过KVO来实时监控。通过completBlock来回调已经执行完毕。

2、NSOperation能通过设置依赖,使任务之间有先后顺序。GCD可以通过栅栏函数来实现。

3、NSOperation能设置并行队列中任务(NSOperation)的优先级(设置NSOperation在queue中的优先级,可以改变操作的执⾏优先级)(优先级的作用是让优先级高的线程需要cpu时就能够得到cpu,而不是让优先级低的线程无法运行,并且可能不止一个CPU,说明:优先级高的任务,调用的几率会更大)

NSOperation是将任务放入队列中来执行的。

GCD是将任务放入队列中,通过同步异步函数来执行(可以多个同步异步函数)。

而GCD是设置不同任务队列(queue)的优先级,要实现block的优先级,需要很多代码。

GCD更简洁

推荐阅读更多精彩内容