0x7157bf0>{name=(null),num=4}
可以看出,这4个block是并发执行的,也就是在不同线程中执行的,num属性可以看成是线程的id
四、自定义NSOperation
1.简介
如果NSInvocationOperation和NSBlockOperation对象不能满足需求,你可以直接继承NSOperation,并添加任何你想要的行为。
继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。
定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件;对于并发NSOperation,你必须重写NSOperation的多个基本方法进行实现(这里暂时先介绍非并发的NSOperation)
2.非并发的NSOperation
比如叫做DownloadOperation,用来下载图片
1>继承NSOperation,重写main方法,执行主任务
DownloadOperation.h
复制代码代码如下:
#import
@protocolDownloadOperationDelegate;
@interfaceDownloadOperation:
NSOperation
//图片的url路径
@property(nonatomic,copy)NSString*imageUrl;
//代理
@property(nonatomic,retain)iddelegate;
-(id)initWithUrl:
(NSString*)urldelegate:
(id)delegate;
@end
复制代码代码如下:
//图片下载的协议
@protocolDownloadOperationDelegate
-(void)loadFinishWithImage:
(UIImage*)image;
@end
DownloadOperation.m
复制代码代码如下:
#import"DownloadOperation.h"
@implementationDownloadOperation
@synthesizedelegate=_delegate;
@synthesizeimageUrl=_imageUrl;
//初始化
-(id)initWithUrl:
(NSString*)urldelegate:
(id)delegate{
if(self=[superinit]){
self.imageUrl=url;
self.delegate=delegate;
}
returnself;
}
//释放内存
-(void)dealloc{
[superdealloc];
[_delegaterelease];
[_imageUrlrelease];
}
//执行主任务
-(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=[NSURLURLWithString:
self.imageUl];
NSData*imageData=[NSDatadataWithContentsOfURL:
url];
if(self.isCancelled){
url=nil;
imageData=nil;
return;
}
//初始化图片
UIImage*image=[UIImageimageWithData:
imageData];
if(self.isCancelled){
image=nil;
return;
}
if([self.delegaterespondsToSelector:
@selector(downloadFinishWithImage:
)]){
//把图片数据传回到主线程
[(NSObject*)self.delegateperformSelectorOnMainThread:
@selector(downloadFinishWithImage:
)withObject:
imagewaitUntilDone:
NO];
}
}
}
NSOperationQueue
一、简介
一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。
也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。
创建一个操作队列:
复制代码代码如下:
NSOperationQueue*queue=[[NSOperationQueuealloc]init];
二、添加NSOperation到NSOperationQueue中
1.添加一个operation
复制代码代码如下:
[queueaddOperation:
operation];
2.添加一组operation
复制代码代码如下:
[queueaddOperations:
operationswaitUntilFinished:
NO];
3.添加一个block形式的operation
复制代码代码如下:
[queueaddOperationWithBlock:
^(){
NSLog(@"执行一个新的操作,线程:
%@",[NSThreadcurrentThread]);
}];
NSOperation添加到queue之后,通常短时间内就会得到运行。
但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。
注意:
NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。
因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。
你只能查看NSOperation对象的状态,比如是否正在运行、等待运行、已经完成等
三、添加NSOperation的依赖对象
1.当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。
另外,通过removeDependency方法来删除依赖对象。
复制代码代码如下:
[operation2addDependency:
operation1];
依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖,因此完全可以在不同的queue之间的NSOperation对象创建依赖关系
唯一的限制是不能创建环形依赖,比如A依赖B,B依赖A,这是错误的
2.依赖关系会影响到NSOperation对象在queue中的执行顺序,看下面的例子:
1>没有设置依赖关系
复制代码代码如下:
NSOperationQueue*queue=[[NSOperationQueuealloc]init];
NSBlockOperation*operation1=[NSBlockOperationblockOperationWithBlock:
^(){
NSLog(@"执行第1次操作,线程:
%@",[NSThreadcurrentThread]);
}];
NSBlockOperation*operation2=[NSBlockOperationblockOperationWithBlock:
^(){
NSLog(@"执行第2次操作,线程:
%@",[NSThreadcurrentThread]);
}];
[queueaddOperation:
operation1];
[queueaddOperation:
operation2];
打印信息:
2013-02-0300:
21:
35.024thread[5616:
3d13]执行第1次操作,线程:
0x7658570>{name=(null),num=3}2013-02-0300:
21:
35.063thread[5616:
1303]执行第2次操作,线程:
0x765a2e0>{name=(null),num=4}
可以看出,默认是按照添加顺序执行的,先执行operation1,再执行operation2
2>设置了依赖关系
复制代码代码如下:
NSOperationQueue*queue=[[NSOperationQueuealloc]init];
NSBlockOperation*operation1=[NSBlockOperationblockOperationWithBlock:
^(){
NSLog(@"执行第1次操作,线程:
%@",[NSThreadcurrentThread]);
}];
NSBlockOperation*operation2=[NSBlockOperationblockOperationWithBlock:
^(){
NSLog(@"执行第2次操作,线程:
%@",[NSThreadcurrentThread]);
}];
//operation1依赖于operation2
[operation1addDependency:
operation2];
[queueaddOperation:
operation1];
[queueaddOperation:
operation2];
打印信息:
2013-02-0300:
24:
16.260thread[5656:
1b03]执行第2次操作,线程:
0x7634490>{name=(null),num=3}2013-02-0300:
24:
16.285thread[5656:
1303]执行第1次操作,线程:
0x9138b50>{name=(null),num=4}
可以看出,先执行operation2,再执行operation1
四、修改Operations的执行顺序
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:
是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。
优先级等级则是operation对象本身的一个属性。
默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:
方法来提升或降低operation对象的优先级。
优先级只能应用于相同queue中的operations。
如果应用有多个operationqueue,每个queue的优先级等级是互相独立的。
因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:
优先级不能替代依赖关系,优先级只是对已经准备好的operations确定执行顺序。
先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
五、设置队列的最大并发操作数量
队列的最大并发操作数量,意思是队列中最多同时运行几条线程
虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个Operation。
setMaxConcurrentOperationCount:
方法可以配置queue的最大并发操作数量。
设为1就表示queue每次只能执行一个操作。
不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。
因此串行化的operationqueue并不等同于GCD中的串行dispatchqueue
复制代码代码如下:
//每次只能执行一个操作
queue.maxConcurrentOperationCount=1;
//或者这样写
[queuesetMaxConcurrentOperationCount:
1];
六、取消Operations
一旦添加到operationqueue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。
你可以调用Operation对象的cancel方法取消单个操作,也可以调用operationqueue的cancelAllOperations方法取消当前queue中的所有操作。
复制代码代码如下:
//取消单个操作
[operationcancel];
//取消queue中所有的操作
[queuecancelAllOperations];
七、等待Options完成
为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。
如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。
通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。
绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。
阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
复制代码代码如下:
//会阻塞当前线程,等到某个operation执行完毕
[operationwaitUntilFinished];
除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。
注意:
在等待一个queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。
复制代码代码如下:
//阻塞当前线程,等待queue的所有操作执行完毕
[queuewaitUntilAllOperationsAreFinished];
八、暂停和继续queue
如果你想临时暂停Operations的执行,可以使用queue的setSuspended:
方法暂停queue。
不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。
你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。
稍后根据用户的请求,可以再次调用setSuspended:
方法继续queue中operation的执行
复制代码代码如下:
//暂停queue
[queuesetSuspended:
YES];
//继续queue
[queuesetSuspended: