iOS多线程编程Part2/3-NSOperation

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

多线程编程Part 1介绍了NSThread以及NSRunLoop,这篇Blog介绍另一种并发编程技术:NSOperation。

NSOperation & NSOperationQueue从头文件NSOperation.h来看接口是非常的简洁,NSOperation本身是一个抽象类,定义了一个要执行的工作,NSOperationQueue是一个工作队列,当工作加入到队列后,NSOperationQueue会自动按照优先顺序及工作的从属依赖关系(如果有的话)组织执行。

NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。如果有必要也可以不将NSOperation加入到一个NSOperationQueue中去执行,直接调用起-start也可以直接执行。

在继承NSOpertaion后,对于非并发的工作,只需要实现NSOperation子类的main方法:

1234567891011

-(void)main{@try{// 处理工作任务}@catch(...){// 处理异常,但是不能再重新抛出异常}}

由于NSOperation的工作是可以取消Cancel的,那么你在main方法处理工作时就需要不断轮询[self isCancelled]确认当前的工作是否被取消了。

如果要支持并发工作,那么NSOperation子类需要至少override这四个方法:

startisConcurrentisExecutingisFinished实现了一个基于Operation的下载器,在Sample Code中可以下载。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172

- (void)operationDidStart{[self.lock lock];NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.URLcachePolicy:NSURLRequestReloadIgnoringCacheDatatimeoutInterval:self.timeoutInterval];[request setHTTPMethod: @"GET"];self.connection =[[NSURLConnection alloc] initWithRequest:requestdelegate:selfstartImmediately:NO];[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];[self.connection start];[self.lock unlock];}- (void)operationDidFinish{[self.lock lock];[self willChangeValueForKey:@"isFinished"];[self willChangeValueForKey:@"isExecuting"];self.executing = NO;self.finished = YES;[self didChangeValueForKey:@"isExecuting"];[self didChangeValueForKey:@"isFinished"];[self.lock unlock];}- (void)start{[self.lock lock];if ([self isCancelled]){[self willChangeValueForKey:@"isFinished"];self.finished = YES;[self didChangeValueForKey:@"isFinished"];return;}[self willChangeValueForKey:@"isExecuting"];[self performSelector:@selector(operationDidStart) onThread:[[self class] networkThread] withObject:nil waitUntilDone:NO];self.executing = YES;[self didChangeValueForKey:@"isExecuting"];[self.lock unlock];}- (void)cancel{[self.lock lock];[super cancel];if (self.connection){[self.connection cancel];self.connection = nil;}[self.lock unlock];}- (BOOL)isConcurrent {return YES;}- (BOOL)isExecuting {return self.executing;}- (BOOL)isFinished {return self.finished;}

start方法是工作的入口,通常是你用来设置线程或者其他执行工作任务需要的运行环境的,注意不要调用[super start];isConcurrent是标识这个Operation是否是并发执行的,这里曾经是个坑,如果你没有实现isConcurrent,默认是返回NO,那么你的NSOperation就不是并发执行而是串行执行的,不过在iOS5.0和OS X10.6之后,已经会默认忽略这个返回值,最终和Queue的maxConcurrentOperationCount最大并发操作值相关;isExecuting和isFinished是用来报告当前的工作执行状态情况的,注意必须是线程访问安全的。

注意你的实现要发出合适的KVO通知,因为如果你的NSOperation实现需要用到工作依赖从属特性,而你的实现里没有发出合适的“isFinished”KVO通知,依赖你的NSOperation就无法正常执行。NSOperation支持KVO的属性有:

isCancelledisConcurrentisExecutingisFinishedisReadydependenciesqueuePRioritycompletionBlock当然也不是说所有的KVO通知都需要自己去实现,例如通常你用不到addObserver到你工作的“isCancelled”属性,你只需要直接调用cancel方法就可以取消这个工作任务。

实现NSOperation子类后,可以直接调用start或者添加到一个NSOperationQueue里:

123

NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:downloader];

NSOperation和NSOperationQueue其他特性工作是有优先级的,可以通过NSOperation的一下两个接口读取或者设置:

12

- (NSOperationQueuePriority)queuePriority;- (void)setQueuePriority:(NSOperationQueuePriority)p;

工作之间也可有从属依赖关系,只有依赖的工作完成后才会执行:

12

- (void)addDependency:(NSOperation *)op;- (void)removeDependency:(NSOperation *)op;

还可以通过下面接口设置运行NSOpration的子线程优先级:

1

- (void)setQueuePriority:(NSOperationQueuePriority)priority;

如果要设置Queue的并发操作数:

1

- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

iOS4之后还可以往NSOperation上添加一个结束block,用于在工作执行结束之后的操作:

1

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

如果需要阻塞等待NSOperation工作结束(别在主线程这么干),可以使用接口:

1

- (void)waitUntilFinished;

NSOperationQueue除了添加NSOperation外,也支持直接添加一个Block(iOS4之后):

1

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

NSOperationQueue可以取消所有添加的工作:

1

- (void)cancelAllOperations;

也可以阻塞式的等待所有工作结束(别在主线程这么干):

1

- (void)waitUntilAllOperationsAreFinished;

在NSOperation对象中获得被添加的NSOperationQueue队列:

1

+ (id)currentQueue

要获得一个绑定在主线程的NSOperationQueue队列:

1

+ (id)mainQueue

还有些接口参考头文件NSOperation.h和NSOperation Class Reference,Apple的Class Reference文档描述还是很清晰的。

NSInvocationOperation & NSBlockOperation其实除非必要,简单的工作完全可以使用官方提供的NSOperation两个子类NSInvocationOperation和NSBlockOperation来实现。

NSInvocationOperation:

1234

NSInvocationOperation* theOp = [[NSInvocationOperation alloc]initWithTarget:selfselector:@selector(myTaskMethod:)object:data];

NSBlockOperation:

1234

NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{NSLog(@"Beginning operation.\n");// Do some work.}];

接口非常简单,一看便会。

Sample Code本文例子放在Github上(工程NSURLConnectionExample中的PTOperationDownloader)。

参考资料Concurrency Programming Guide

NSOperation Class Reference

本文目录前言一、NSInvocationOperation二、NSBlockOperation三、NSOperation的其他用法四、自定义NSOperation

回到顶部

前言1.上一讲简单介绍了NSThread的使用,虽然也可以实现多线程编程,但是需要我们去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销。我们也可以配合使用NSOperation和NSOperationQueue实现多线程编程,实现步骤大致是这样的:

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

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

3> 系统会自动将NSOperation中封装的操作放到一条新线程中执行

在此过程中,我们根本不用考虑线程的生命周期、同步、加锁等问题

下面列举一个应用场景,比如微博的粉丝列表:

每一行的头像肯定要从新浪服务器下载图片后才能显示的,而且是需要异步下载。这时候你就可以把每一行的图片下载操作封装到一个NSOperation对象中,上面有6行,所以要创建6个NSOperation对象,然后添加到NSOperationQueue中,分别下载不同的图片,下载完毕后,回到对应的行将图片显示出来。

2.默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

1> NSInvocationOperation

2> NSBlockOperation

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

这讲先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。

回到顶部

一、NSInvocationOperation1 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:)object:@"mj"] autorelease];2 [operation start];

* 第1行初始化了一个NSInvocationOperation对象,它是基于一个对象和selector来创建操作

* 第2行调用了start方法,紧接着会马上执行封装好的操作,也就是会调用self的run:方法,并且将@"mj"作为方法参数

* 这里要注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。只有将operation放到一个NSOperationQueue中,才会异步执行操作。

回到顶部

二、NSBlockOperation1.同步执行一个操作1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){2 NSLog(@"执行了一个新的操作");3}];4//开始执行任务5 [operation start];

* 第1行初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作

* 第2行调用了start方法,紧接着会马上执行Block中的内容

* 这里还是在当前线程同步执行操作,并没有异步执行

2.并发执行多个操作

1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){2 NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);3}];45 [operation addExecutionBlock:^() {6 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);7}];89 [operation addExecutionBlock:^() {10 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);11}];1213 [operation addExecutionBlock:^() {14 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);15}];1617//开始执行任务18 [operation start];

* 第1行初始化了一个NSBlockOperation对象

* 分别在第5、9、13行通过addExecutionBlock:方法添加了新的操作,包括第1行的操作,一共封装了4个操作

* 在第18行调用start方法后,就会并发地执行这4个操作,也就是会在不同线程中执行

12013-02-0221:38:46.102 thread[4602:c07] 又执行了1个新的操作,线程:<NSThread:0x7121d50>{name = (null), num =1}22013-02-0221:38:46.102 thread[4602:3f03] 又执行了1个新的操作,线程:<NSThread:0x742e1d0>{name = (null), num =5}32013-02-0221:38:46.102 thread[4602:1b03] 执行第1次操作,线程:<NSThread:0x742de50>{name = (null), num =3}42013-02-0221:38:46.102 thread[4602:1303] 又执行了1个新的操作,线程:<NSThread:0x7157bf0>{name = (null), num =4}

可以看出,每个操作所在线程的num值都不一样,说明是不同线程

回到顶部

三、NSOperation的其他用法1.取消操作operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作

[operationcancel];

2.在操作完成后做一些事情如果想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情

operation.completionBlock = ^() {

NSLog(@"执行完毕");

};

当operation封装的操作执行完毕后,就会回调Block里面的内容

回到顶部

四、自定义NSOperation如果NSInvocationOperation和NSBlockOperation不能满足需求,我们可以直接新建子类继承NSOperation,并添加任何需要执行的操作。如果只是简单地自定义NSOperation,只需要重载-(void)main这个方法,在这个方法里面添加需要执行的操作。

下面写个子类DownloadOperation来下载图片

1.继承NSOperation,重写main方法DownloadOperation.h

#import <Foundation/Foundation.h>@protocolDownloadOperationDelegate;@interfaceDownloadOperation : NSOperation//图片的url路径@property (nonatomic, copy) NSString *imageUrl;//代理@property (nonatomic, assign)id<DownloadOperationDelegate>delegate;

- (id)initWithUrl:(NSString *)urldelegate:(id<DownloadOperationDelegate>)delegate;@end//图片下载的协议@protocol DownloadOperationDelegate <NSObject>

- (void)downloadFinishWithImage:(UIImage *)image;@end

DownloadOperation.m

1#import"DownloadOperation.h"23@implementationDownloadOperation4@synthesizedelegate =_delegate;5@synthesize imageUrl =_imageUrl;67//初始化8 - (id)initWithUrl:(NSString *)urldelegate:(id<DownloadOperationDelegate>)delegate{9if (self =[super init]) {10 self.imageUrl =url;11 self.delegate =delegate;12}13returnself;14}15//释放内存16 - (void)dealloc {17[super dealloc];18[_imageUrl release];19}2021//执行主任务22 - (void)main {23//新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池24@autoreleasepool {25//....26}27}28@end

* 在第22行重载了main方法,等会就把下载图片的代码写到这个方法中

* 如果这个DownloadOperation是在异步线程中执行操作,也就是说main方法在异步线程调用,那么将无法访问主线程的自动释放池,所以在第24行创建了一个属于当前线程的自动释放池

2.正确响应取消事件* 默认情况下,一个NSOperation开始执行之后,会一直执行任务到结束,就比如上面的DownloadOperation,默认会执行完main方法中的所有代码。

* NSOperation提供了一个cancel方法,可以取消当前的操作。

* 如果是自定义NSOperation的话,需要手动处理这个取消事件。比如,一旦调用了cancel方法,应该马上终止main方法的执行,并及时回收一些资源。

* 处理取消事件的具体做法是:在main方法中定期地调用isCancelled方法检测操作是否已经被取消,也就是说是否调用了cancel方法,如果返回YES,表示已取消,则立即让main方法返回。

* 以下地方可能需要调用isCancelled方法:

在执行任何实际的工作之前,也就是在main方法的开头。因为取消可能发生在任何时候,甚至在operation执行之前。执行了一段耗时的操作之后也需要检测操作是否已经被取消

1 - (void)main {2//新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池3@autoreleasepool {4if (self.isCancelled)return;56//获取图片数据7 NSURL *url =[NSURL URLWithString:self.imageUrl];8 NSData *imageData =[NSData dataWithContentsOfURL:url];910if(self.isCancelled) {11 url =nil;12 imageData =nil;13return;14}1516//初始化图片17 UIImage *image =[UIImage imageWithData:imageData];1819if(self.isCancelled) {20 image =nil;21return;22}2324if ([self.delegaterespondsToSelector:@selector(downloadFinishWithImage:)]) {25//把图片数据传回到主线程26 [(NSObject *)self.delegateperformSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];27}28}29 }

* 在第4行main方法的开头就先判断operation有没有被取消。如果被取消了,那就没有必要往下执行了

* 经过第8行下载图片后,在第10行也需要判断操作有没有被取消

* 总之,执行了一段比较耗时的操作之后,都需要判断操作有没有被取消

* 图片下载完毕后,在第26行将图片数据传递给了代理(delegate)对象

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航