nsinvocation swiftoperation怎么在一定条件下停止

(window.slotbydup=window.slotbydup || []).push({
id: '2369049',
container: s,
size: '960,48',
display: 'inlay-fix'
个NSInvocationOperation
创建新的生词本
i该生词本已经创建啦!
i不可以出现中文,英文,数字之外的符号哒!
i生词本名称长度不能大于24字符!
i请填写生词本名称!并发编程之Operation Queue和GCD
随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势
并发编程之Operation Queue
随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势,就要采用并发编程,提高CPU的利用率。iOS中并发编程中主要有2种方式Operation Queue和GCD(Grand Central Dispatch)。下面就来先来说一下Operation Queue。
异步调用和并发
在深入之前,首先说说异步调用和并发。这两个概念在并发编程中很容易弄混淆。异步调用是指调用时无需等待结果返回的调用,异步调用往往会触发后台线程处理,比如NSURLConnection的异步网络回调。并发是指多个任务(线程)同时执行。在异步调用的实现中往往采用并发机制,然而并不是所有异步都是并发机制,也有可能是其他机制,比如一些依靠中断进行的操作。
为什么Operation Queue
Operation Queue提供一个面向对象的并发编程接口,支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。
1.面向对象接口
2.支持并发数配置
3.任务优先级调度
4.任务依赖关系
5.线程优先级配置
NSOperation简介
iOS并发编程中,把每个并发任务定义为一个Operation,对应的类名是NSOperation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。目前系统预定义了两个子类:NSInvocationOperation和NSBlockOperation。
NSInvocationOperation
NSInvoationOperation是一个基于对象和selector的Operation,使用这个你只需要指定对象以及任务的selector,如果必要,你还可以设定传递的对象参数。
NSInvocationOperation&*invacationOperation&=&[[NSInvocationOperation&alloc]&initWithTarget:self&selector:@selector(doSomethingWithObj:)&object:obj];&
同时当这个Operation完成后,你还可以获取Operation中Invation执行后返回的结果对象。
id&result&=&[invacationOperation&result];&
NSBlockOperation
在一个Block中执行一个任务,这时我们就需要用到NSBlockOperation。可以通过blockOperationWithBlock:方法来方便地创建一个NSBlockOperation:
NSBlockOperation&*blockOperation&=&[NSBlockOperation&blockOperationWithBlock:^{&&&&&&}];&
运行一个Operation
调用Operation的start方法就可以直接运行一个Operation。
[operation&start];&
start方法用来启动一个Operation任务。同时,Operation提供一个main方法,你的所有任务都应该在main中进行处理。默认的start方法中会先做出一些异常判断然后直接调用main方法。如果需要自定义一个NSOperation必须重载main方法来执行你所想要执行的任务。
@implementation&CustomOperation&&-(void)main&{&&&&@try&{&&&&&&&&&&&}&&&&@catch(...)&{&&&&&&&&&&&}&}&@end&
取消一个Operation
要取消一个Operation,要向Operation对象发送cancel消息:
[operation&cancel];&
当向一个Operation对象发送cancel消息后,并不保证这个Operation对象一定能立刻取消,这取决于你的main中对cancel的处理。如果你在main方法中没有对cancel进行任何处理的话,发送cancel消息是没有任何效果的。为了让Operation响应cancel消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled属性,如果返回YES的话,应释放相关资源并立刻停止继续执行。
创建可并发的Operation
由于默认情况下Operation的start方法中直接调用了main方法,而main方法中会有比较耗时的处理任务。如果我们在一段代码连续start了多个Operation,这些Operation都是阻塞地依次执行完,因为第二个Operation必须等到第一个Operation执行完start内的main并返回。Operation默认都是不可并发的(使用了Operation Queue情况下除外,Operation Queue会独自管理自己的线程),因为默认情况下Operation并不额外创建线程。我们可以通过Operation的isConcurrent方法来判断Operation是否是可并发的。如果要让Operation可并发,我们需要让main在独立的线程中执行,并将isConcurrent返回YES。
@implementation&MyOperation{&&&&&BOOL&&&&&&&&&&&&&BOOL&&&&&&&&&}&&&-&(BOOL)isConcurrent&{&&&&&return&YES;&}&&-&(void)start&{&&&&if&([self&isCancelled])&&&&{&&&&&&&[self&willChangeValueForKey:@&isFinished&];&&&&&&&finished&=&YES;&&&&&&&[self&didChangeValueForKey:@&isFinished&];&&&&&&&return;&&&&}&&&&&[self&willChangeValueForKey:@&isExecuting&];&&&&[NSThread&detachNewThreadSelector:@selector(main)&toTarget:self&withObject:nil];&&&&executing&=&YES;&&&&[self&didChangeValueForKey:@&isExecuting&];&}&&-&(void)main&{&&&&@try&{&&&&&&&&&&&&&&&&&&&[self&willChangeValueForKey:@&isFinished&];&&&&&&&&&[self&willChangeValueForKey:@&isExecuting&];&&&&&&&&&executing&=&NO;&&&&&&&&&finished&=&YES;&&&&&&&&&[self&didChangeValueForKey:@&isExecuting&];&&&&&&&&&[self&didChangeValueForKey:@&isFinished&];&&&&&}&&&&@catch(...)&{&&&&&&&&&&&}&}&&@end&
&当你自定义了start或main方法时,一定要手动的调用一些KVO通知方法,以便让对象的KVO机制可以正常运作。
设置Operation的completionBlock
每个Operation都可以设置一个completionBlock,在Operation执行完成时自动执行这个Block。我们可以在此进行一些完成的处理。completionBlock实现原理是对Operation的isFinnshed字段进行KVO(Key-Value Observing),当监听到isFinnished变成YES时,就执行completionBlock。
<pletionBlock&=&^{&&&&&NSLog(@&finished&);&};&
设置Operation的线程优先级
我们可以为Operation设置一个线程优先级,即threadPriority。那么执行main的时候,线程优先级就会调整到所设置的线程优先级。这个默认值是0.5,我们可以在Operation执行前修改它。
operation.threadPriority&=&0.1;&
注意:如果你重载的start方法,那么你需要自己来配置main执行时的线程优先级和threadPriority字段保持一致。
Operation状态变化
我们可以通过KVO机制来监听Operation的一下状态改变,比如一个Operation的执行状态或完成状态。这些状态的keypath包括以下几个:
isCancelled
isConcurrent
isExecuting
isFinished
dependencies
queuePriority
completionBlock
NSOperationQueue
NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。同时Operation和Operation Queue提供了很多可配置选项。Operation Queue的实现中,创建了一个或多个可管理的线程,为队列中的Operation提供可高度自定的执行环境。
Operation的依赖关系
有时候我们对任务的执行顺序有要求,一个任务必须在另一个任务执行之前完成,这就需要用到Operation的依赖(Dependency)属性。我们可以为每个Operation设定一些依赖的另外一些Operation,那么如果依赖的Operation没有全部执行完毕,这个Operation就不会被执行。
[operation&addDependency:anotherOperation];&[operation&removeDependency:anotherOperation];&
如果将这些Operation和它所依赖的Operation加如队列中,那么Operation只有在它依赖的Operation都执行完毕后才可以被执行。这样我们就可以方便的控制Operation执行顺序。
Operation在队列中执行的优先级
Operation在队列中默认是按FIFO(First In First Out)顺序执行的。同时我们可以为单个的Operation设置一个执行的优先级,打乱这个顺序。当Queue有空闲资源执行新的Operation时,会优先执行当前队列中优先级最高的待执行Operation。
最大并发Operation数目
在一个Operation Queue中是可以同时执行多个Operation的,Operation Queue会动态的创建多个线程来完成相应Operation。具体的线程数是由Operation Queue来优化配置的,这一般取决与系统CPU的性能,比如CPU的核心数,和CPU的负载。但我们还是可以设置一个最大并发数的,那么Operation Queue就不会创建超过最大并发数量的线程。
NSOperationQueue&*queue&=&[[NSOperationQueue&alloc]&init];&queue.maxConcurrentOperationCount&=&1;&
如果我们将maxConcurrentOperationCount设置为1,那么在队列中每次只能执行一个任务。这就是一个串行的执行队列了。
Simple Code
下面我写了一个简单的Simple Code来说明一下Operation和Operation Queue。
NSBlockOperation&*operation5s&=&[NSBlockOperation&blockOperationWithBlock:^{&&&&&NSLog(@&operation5s&begin&);&&&&&sleep(5);&&&&&NSLog(@&operation5s&end&);&}];&operation5s.queuePriority&=&NSOperationQueuePriorityH&NSBlockOperation&*operation1s&=&[NSBlockOperation&blockOperationWithBlock:^{&&&&&NSLog(@&operation1s&begin&);&&&&&sleep(1);&&&&&NSLog(@&operation1s&end&);&}];&NSBlockOperation&*operation2s&=&[NSBlockOperation&blockOperationWithBlock:^{&&&&&NSLog(@&operation2s&begin&);&&&&&sleep(2);&&&&&NSLog(@&operation2s&end&);&}];&&<pletionBlock&=&^{&&&&&NSLog(@&operation1s&finished&in&completionBlock&);&};&&NSOperationQueue&*queue&=&[[NSOperationQueue&alloc]&init];&queue.maxConcurrentOperationCount&=&1;&[queue&addOperation:operation1s];&[queue&addOperation:operation2s];&[queue&addOperation:operation5s];&[queue&waitUntilAllOperationsAreFinished];&
运行这段代码,我得到了一下输出结果:
operation1s&begin&operation1s&end&operation5s&begin&operation1s&finished&in&completionBlock&operation5s&end&operation2s&begin&operation2s&end&
为了更好的展示队列优先级效果,我把queue的maxConcurrentOperationCount设置为1,以便任务一个一个的执行。从上面日志可以看出,第一个operation1s执行完毕后,会执行operation5s,而不是operation2s,因为operation5s的queuePriority是NSOperationQueuePriorityHigh。而第一个线程总是会第一个执行。在看看2-4行,我们可以看出operation1s的completionBlock比operation5s晚开始执行,说明它不在operation1s的线程中执行的。正如前面所说,completionBlock是通过KVO监听执行,一般会运行在监听所在线程,而不是Operation执行的线程。
当一个Operation被加入Queue中后,请不要对这个Operation再进行任何修改。因为一旦加入Queue,它随时就有可能会被执行,对它的任何修改都有可能导致它的运行状态不可控制。
threadPriority仅仅影响了main执行时的线程优先级,其他的方法包括completionBlock都是以默认的优先级来执行的。如果自定义的话,也要注意在main执行前设置好threadPriority,执行完毕后要还原默认线程优先级。
经测试,Operation的threadPriority字段只有在Operation单独执行时有效,在Operation Queue中是无效的。
第一个加入到Operation Queue中的Operation,无论它的优先级有多么低,总是会第一个执行。
CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!
请搜索微信号“CocoaChina”关注我们!
共2页: 上一页
关注微信 每日推荐
扫一扫 浏览移动版3863人阅读
NSOperation
NSInvocationOperation
IOS中支持多线程操作,使用NSThread和NSInvocationOperation可以完成多线程功能。多线程的功能主要是为了防止阻塞主线程的工作(主要是UI操作和显示),使一些耗时的的操作在另一个线程中完成,完成后可以通知主线程来进行UI上的更新。多线程功能在实际开发中用的很多,最典型的就是网络请求和处理操作,下面主要来讨论一下Cocoa中的NSThread和NSInvocationOperation:
一、NSThread
创建NSThread主要有两种方式:
1.使用类方法创建
[NSThread&detachNewThreadSelector:@selector(doInBackgroud) toTarget:self&withObject:nil];
2.使用传统方式创建
NSThread&*thread = [[NSThreadalloc]&initWithTarget:self&selector:@selector(doInBackgroud)&object:nil];
[thread&start];
两种方式的区别:
1.第一种方式会立即调用并执行线程,第二种必须调用start方法后才会开始执行线程,在此之前可以对线程进行一些设置,比如线程优先级等。第二种方式与Java中线程的使用类&#20284;。
2.使用类方法(Convenient Method)创建的线程不需要进行内存清理,而使用initWithTarget方法创建的线程需要当retainCount为0时调用release方法释放内存。
//在另一个线程中运行的方法
-(void)doInBackgroud
&&&&NSAutoreleasePool&*releasePool = [[NSAutoreleasePoolalloc]&init];
&&&&//do someting...
&&& [releasePool&release];
多线程中执行的方法必须自行进行内存管理,否则会出现警告信息。
运行程序可以看到打印信息:
11:03:21.470 ThreadTest[518:f803] Thread is start...
11:03:21.471 ThreadTest[518:1291b] Thread is running...
11:03:24.471 ThreadTest[518:1291b] Thread is done…
完整代码如下:
二、NSOperation
NSOperation是Cocoa中的一个抽象类,用来封装单个任务和代码执行一项操作,由于是抽象类,所以不能直接实例化使用,必须定义子类继承该抽象类来实现,比较常用的NSOperation的子类有NSInvocationOperation,另外,也可以自己继承NSOperation来实现线程的操作。
另外会使用到操作队列NSOperationQueue,它相当于一个线程队列或者可以叫做线程池,可以顺序执行队列中的操作,也可以设置队列中操作的优先级。
.m实现文件:
使用方法如下:
运行结果和上面结果一样。
三、NSInvocationOperation
NSOperation的子类NSInvocationOperation提供了一套简单的多线程编程方法,是IOS多线程编程中最简单的一种实现方式。直接看代码:
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:34673次
排名:千里之外在 头文件中声明一个 队列和两个 :&
#import &UIKit/UIKit.h&
@interface Running_Tasks_Asynchronously_with_OperationsAppDelegate
: UIResponder &UIApplicationDelegate& @property (nonatomic, strong) UIWindow *
@property (nonatomic, strong) NSOperationQueue *operationQ @property (nonatomic, strong) NSInvocationOperation *firstO @property (nonatomic, strong) NSInvocationOperation *secondO @end
的实现文件文件如下:&
#import "Running_Tasks_Asynchronously_with_OperationsAppDelegate.h" @implementation Running_Tasks_Asynchronously_with_OperationsAppDelegate @synthesize window = _
@synthesize firstO
@synthesize secondO
@synthesize operationQ
- (void) firstOperationEntry:(id)paramObject{
NSLog(@"%s", __FUNCTION__);
NSLog(@"Parameter Object = %@", paramObject); NSLog(@"Main Thread = %@", [NSThread mainThread]); NSLog(@"Current Thread = %@", [NSThread currentThread]);
- (void) secondOperationEntry:(id)paramObject{
NSLog(@"%s", __FUNCTION__);
NSLog(@"Parameter Object = %@", paramObject); NSLog(@"Main Thread = %@", [NSThread mainThread]); NSLog(@"Current Thread = %@", [NSThread currentThread]);
- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSNumber *firstNumber = [NSNumber numberWithInteger:111]; NSNumber *secondNumber = [NSNumber numberWithInteger:222];
self.firstOperation =[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(firstOperationEntry:)
object:firstNumber]; self.secondOperation = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(secondOperationEntry:) object:secondNumber];
self.operationQueue = [[NSOperationQueue alloc] init]; /* Add the operations to the queue */
[self.operationQueue addOperation:self.firstOperation]; [self.operationQueue addOperation:self.secondOperation];
NSLog(@"Main thread is here");
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
return YES;
在实现的代码里面都发生了什么呢: ?有两个方法:和 ,每个方法都接收一个对象&
作为参数,并且在控制台窗口打印出当前线程、主线程和参数。这两个入口函数的
将被添加到一个 队列中。 ?我们初始化两个 类型对象,并给每个 设置目标
入口点,如之前所述。 ?然后我们初始化一个 类型对象。(当然也可以在入口方法前面创
建)队列对象将负责管理 对象的并发。 ? 我们调用 的实例方法 把每个 添加
到 队列中。在这里,队列可能会也可能不会立即通过 的 方法启动 。但是,需要牢记重要的一点:添加至 队列后,你不能手动启动 ,必须交由 队列负责。
现在,我们运行一次示例代码,在控制台窗口可以看到如下结果:
如果我们子类化了一个 类,并且把这个子类的实例对象添加到了 队列,我们需要做稍微的改动。记住以下几点:
? 由于当把 的子类对象添加到一个 队列中,该对象会异步运行。由此,你必须 的实例方法 ,在该方法中返回 。? 在 方法里面执行 任务之前,需要定期的调用 方法来检测该函数的返回值,以确定是退出 方法还是开始运行 。在这里,当 添加到队列中后,的 方法将会被 队列调用,方法中,调用方法确定 是否被取消。如果 被取消了,只需要从
方法中简单的返回即可。如果没被取消,会在 方法中调用 方法。
? 在 实现部分中 函数,函数将被 执行。在这个函数里面确保分配和初始化 ,并且在返回之前释放这个 。
? 重载 的 和 方法,这两个函数返回对应的 值,代表 是执行完毕还是在执行中。
下面是我们的 声明文件:&
#import &Foundation/Foundation.h&
@interface SimpleOperation : NSOperation
/* Designated Initializer */
- (id) initWithObject:(NSObject *)paramO @end
The implementation of the operation is as follows:
#import "SimpleOperation.h" @implementation SimpleOperation NSObject *givenO
- (id) init {
NSNumber *dummyObject = [NSNumber numberWithInteger:123]; return([self initWithObject:dummyObject]);
- (id) initWithObject:(NSObject *)paramObject{
self = [super init];
if (self != nil){
/* Keep these values for the main method */
givenObject = paramO
return(self);
- (void) start {
/* If we are cancelled before starting, then
we have to return immediately and generate the
required KVO notifications */
if ([self isCancelled]){
/* If this operation *is* cancelled */
/* KVO compliance */
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
/* If this operation is *not* cancelled */
/* KVO compliance */
[self willChangeValueForKey:@"isExecuting"]; executing = YES;
/* Call the main method from inside the start method */ [self main];
[self didChangeValueForKey:@"isExecuting"];
- (void) main {
@autoreleasepool {
/* Keep a local variable here that must get set to YES whenever we are done with the task */
BOOL taskIsFinished = NO;
/* Create a while loop here that only exists
if the taskIsFinished variable is set to YES or
the operation has been cancelled */
while (taskIsFinished == NO &&
[self isCancelled] == NO){
/* Perform the task here */
NSLog(@"%s", __FUNCTION__);
NSLog(@"Parameter Object = %@", givenObject); NSLog(@"Main Thread = %@", [NSThread mainThread]); NSLog(@"Current Thread = %@", [NSThread currentThread]); /* Very important. This way we can get out of the
loop and we are still complying with the cancellation
rules of operations */
taskIsFinished = YES;
/* KVO compliance. Generate the
required KVO notifications */
[self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; finished = YES;
executing = NO;
[self didChangeValueForKey:@"isFinished"]; [self didChangeValueForKey:@"isExecuting"]; }
@catch (NSException * e) { NSLog(@"Exception %@", e);
- (BOOL) isConcurrent{
return YES;
- (BOOL) isFinished{
/* Simply return the value */
- (BOOL) isExecuting{
/* Simply return the value */
现在可以在其他任何类中使用上面定义的这个 类了,比如在 中。下面是 的声明,使用了新的 类,并将其添加到了新的 队列中:&
#import &UIKit/UIKit.h&
@class SimpleO
@interface Running_Tasks_Asynchronously_with_OperationsAppDelegate : UIResponder &UIApplicationDelegate&
@property (nonatomic, strong) UIWindow *
@property (nonatomic, strong) NSOperationQueue *operationQ @property (nonatomic, strong) SimpleOperation *firstO @property (nonatomic, strong) SimpleOperation *secondO
的实现部分如下:&
#import "Running_Tasks_Asynchronously_with_OperationsAppDelegate.h" #import "SimpleOperation.h"
@implementation Running_Tasks_Asynchronously_with_OperationsAppDelegate @synthesize window = _
@synthesize firstO
@synthesize secondO
@synthesize operationQ
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSNumber *firstNumber = [NSNumber numberWithInteger:111]; NSNumber *secondNumber = [NSNumber numberWithInteger:222]; self.firstOperation = [[SimpleOperation alloc]
initWithObject:firstNumber];
self.secondOperation = [[SimpleOperation alloc] initWithObject:secondNumber];
self.operationQueue = [[NSOperationQueue alloc] init]; /* Add the operations to the queue */ [self.operationQueue addOperation:self.firstOperation]; [self.operationQueue addOperation:self.secondOperation]; NSLog(@"Main thread is here");
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
return YES;
打印到控制台窗口的结果与之前使用并发 类似:&
阅读(...) 评论()超详细!iOS 并发编程之 Operation Queues
招聘信息:
作者: 授权本站转载。现如今移动设备也早已经进入了多核心 CPU 时代,并且随着时间的推移,CPU 的核心数只会增加不会减少。而作为软件开发者,我们需要做的就是尽可能地提高应用的并发性,来充分利用这些多核心 CPU 的性能。在 iOS 开发中,我们主要可以通过 Operation Queues、Dispatch Queues 和 Dispatch Sources 来提高应用的并发性。本文将主要介绍 Operation Queues 的相关知识,另外两个属于 Grand Central Dispatch(以下正文简称 GCD )的范畴,将会在后续的文章中进行介绍。由于本文涉及的内容较多,所以建议读者先提前了解一下本文的目录结构,以便对本文有一个宏观的认识:基本概念术语串行 vs. 并发同步 vs. 异步队列 vs. 线程iOS 的并发编程模型Operation Queues vs. Grand Central Dispatch (GCD)关于 Operation 对象并发 vs. 非并发 Operation创建 NSInvocationOperation 对象创建 NSBlockOperation 对象自定义 Operation 对象执行主任务响应取消事件配置并发执行的 Operation维护 KVO 通知定制 Operation 对象的执行行为配置依赖关系修改 Operation 在队列中的优先级修改 Operation 执行任务线程的优先级设置 Completion Block执行 Operation 对象添加 Operation 到 Operation Queue 中手动执行 Operation取消 Operation等待 Operation 执行完成暂停和恢复 Operation Queue总结基本概念在正式开始介绍 Operation Queues 的相关知识前,我想先介绍几个在 iOS 并发编程中非常容易混淆的基本概念,以帮助读者更好地理解本文。注,本文中的 Operation Queues 指的是 NSOperation 和 NSOperationQueue 的统称。术语首先,我们先来了解一下在 iOS 并发编程中非常重要的三个术语,这是我们理解 iOS 并发编程的基础:进程(process),指的是一个正在运行中的可执行文件。每一个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出时,这个进程就结束了;线程(thread),指的是一个独立的代码执行路径,也就是说线程是代码执行路径的最小分支。在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads ;任务(task),指的是我们需要执行的工作,是一个抽象的概念,用通俗的话说,就是一段代码。串行 vs. 并发从本质上来说,串行和并发的主要区别在于允许同时执行的任务数量。串行,指的是一次只能执行一个任务,必须等一个任务执行完成后才能执行下一个任务;并发,则指的是允许多个任务同时执行。同步 vs. 异步同样的,同步和异步操作的主要区别在于是否等待操作执行完成,亦即是否阻塞当前线程。同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。队列 vs. 线程有一些对 iOS 并发编程模型不太了解的同学可能会对队列和线程产生混淆,不清楚它们之间的区别与联系,因此,我觉得非常有必要在这里简单地介绍一下。在 iOS 中,有两种不同类型的队列,分别是串行队列和并发队列。正如我们上面所说的,串行队列一次只能执行一个任务,而并发队列则可以允许多个任务同时执行。iOS 系统就是使用这些队列来进行任务调度的,它会根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动地管理。iOS 的并发编程模型在其他许多语言中,为了提高应用的并发性,我们往往需要自行创建一个或多个额外的线程,并且手动地管理这些线程的生命周期,这本身就已经是一项非常具有挑战性的任务了。此外,对于一个应用来说,最优的线程个数会随着系统当前的负载和低层硬件的情况发生动态变化。因此,一个单独的应用想要实现一套正确的多线程解决方案就变成了一件几乎不可能完成的事情。而更糟糕的是,线程的同步机制大幅度地增加了应用的复杂性,并且还存在着不一定能够提高应用性能的风险。然而,值得庆幸的是,在 iOS 中,苹果采用了一种比传统的基于线程的系统更加异步的方式来执行并发任务。与直接创建线程的方式不同,我们只需定义好要调度的任务,然后让系统帮我们去执行这些任务就可以了。我们可以完全不需要关心线程的创建与销毁、以及多线程之间的同步等问题,苹果已经在系统层面帮我们处理好了,并且比我们手动地管理这些线程要高效得多。因此,我们应该要听从苹果的劝告,珍爱生命,远离线程。不过话又说回来,尽管队列是执行并发任务的首先方式,但是毕竟它们也不是什么万能的灵丹妙药。所以,在以下三种场景下,我们还是应该直接使用线程的:用线程以外的其他任何方式都不能实现我们的特定任务;必须实时执行一个任务。因为虽然队列会尽可能快地执行我们提交的任务,但是并不能保证实时性;你需要对在后台执行的任务有更多的可预测行为。Operation Queues vs. Grand Central Dispatch (GCD)简单来说,GCD 是苹果基于 C 语言开发的,一个用于多核编程的解决方案,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。而 Operation Queues 则是一个建立在 GCD 的基础之上的,面向对象的解决方案。它使用起来比 GCD 更加灵活,功能也更加强大。下面简单地介绍了 Operation Queues 和 GCD 各自的使用场景:Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。关于 Operation 对象在 iOS 开发中,我们可以使用 NSOperation 类来封装需要执行的任务,而一个 operation 对象(以下正文简称 operation )指的就是 NSOperation 类的一个具体实例。NSOperation 本身是一个抽象类,不能直接实例化,因此,如果我们想要使用它来执行具体任务的话,就必须创建自己的子类或者使用系统预定义的两个子类,NSInvocationOperation 和 NSBlockOperation 。NSInvocationOperation :我们可以通过一个 object 和 selector 非常方便地创建一个 NSInvocationOperation ,这是一种非常动态和灵活的方式。假设我们已经有了一个现成的方法,这个方法中的代码正好就是我们需要执行的任务,那么我们就可以在不修改任何现有代码的情况下,通过方法所在的对象和这个现有方法直接创建一个 NSInvocationOperation 。NSBlockOperation :我们可以使用 NSBlockOperation 来并发执行一个或多个 block ,只有当一个 NSBlockOperation 所关联的所有 block 都执行完毕时,这个 NSBlockOperation 才算执行完成,有点类似于 dispatch_group 的概念。另外,所有的 operation 都支持以下特性:支持在 operation 之间建立依赖关系,只有当一个 operation 所依赖的所有 operation 都执行完成时,这个 operation 才能开始执行;支持一个可选的 completion block ,这个 block 将会在 operation 的主任务执行完成时被调用;支持通过 KVO 来观察 operation 执行状态的变化;支持设置执行的优先级,从而影响 operation 之间的相对执行顺序;支持取消操作,可以允许我们停止正在执行的 operation 。并发 vs. 非并发 Operation通常来说,我们都是通过将 operation 添加到一个 operation queue 的方式来执行 operation 的,然而这并不是必须的。我们也可以直接通过调用 start 方法来执行一个 operation ,但是这种方式并不能保证 operation 是异步执行的。NSOperation 类的 isConcurrent 方法的返回值标识了一个 operation 相对于调用它的 start 方法的线程来说是否是异步执行的。在默认情况下,isConcurrent 方法的返回值是 NO ,也就是说会阻塞调用它的 start 方法的线程。如果我们想要自定义一个并发执行的 operation ,那么我们就必须要编写一些额外的代码来让这个 operation 异步执行。比如,为这个 operation 创建新的线程、调用系统的异步方法或者其他任何方式来确保 start 方法在开始执行任务后立即返回。在绝大多数情况下,我们都不需要去实现一个并发的 operation 。如果我们一直是通过将 operation 添加到 operation queue 的方式来执行 operation 的话,我们就完全没有必要去实现一个并发的 operation 。因为,当我们将一个非并发的 operation 添加到 operation queue 后,operation queue 会自动为这个 operation 创建一个线程。因此,只有当我们需要手动地执行一个 operation ,又想让它异步执行时,我们才有必要去实现一个并发的 operation 。创建 NSInvocationOperation 对象正如上面提到的,NSInvocationOperation 是 NSOperation 类的一个子类,当一个 NSInvocationOperation 开始执行时,它会调用我们指定的 object 的 selector 方法。通过使用 NSInvocationOperation 类,我们可以避免为每一个任务都创建一个自定义的子类,特别是当我们在修改一个已经存在的应用,并且这个应用中已经有了我们需要执行的任务所对应的 object 和 selector 时非常有用。下面的示例代码展示了如何通过 object 和 selector 创建一个 NSInvocationOperation 对象。说明,本文中的所有示例代码都可以在这里
找到,每一个类都有与之对应的测试类,充当 client 的角色,建议你在看完一个小节的代码时,运行一下相应的测试用例,观察打印的结果,以加深理解。@implementation&OQCreateInvocationOperation
-&(NSInvocationOperation&*)invocationOperationWithData:(id)data&{
&&&&return&[[NSInvocationOperation&alloc]&initWithTarget:self&selector:@selector(myTaskMethod1:)&object:data];
-&(void)myTaskMethod1:(id)data&{
&&&&NSLog(@"Start&executing&%@&with&data:&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&data,&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&sleep(3);
&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
@end另外,我们在前面也提到了,NSInvocationOperation 类的使用可以非常的动态和灵活,其中比较显著的一点就是我们可以根据上下文动态地调用 object 的不同 selector 。比如说,我们可以根据用户的输入动态地执行不同的 selector :-&(NSInvocationOperation&*)invocationOperationWithData:(id)data&userInput:(NSString&*)userInput&{
&&&&NSInvocationOperation&*invocationOperation&=&[self&invocationOperationWithData:data];
&&&&if&(userInput.length&==&0)&{
&&&&&&&&invocationOperation.invocation.selector&=&@selector(myTaskMethod2:);
&&&&return&invocationO
-&(void)myTaskMethod2:(id)data&{
&&&&NSLog(@"Start&executing&%@&with&data:&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&data,&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&sleep(3);
&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
}创建 NSBlockOperation 对象NSBlockOperation 是 NSOperation 类的另外一个系统预定义的子类,我们可以用它来封装一个或多个 block 。我们知道 GCD 主要就是用来进行 block 调度的,那为什么我们还需要 NSBlockOperation 类呢?一般来说,有以下两个场景我们会优先使用 NSBlockOperation 类:当我们在应用中已经使用了 Operation Queues 且不想创建 Dispatch Queues 时,NSBlockOperation 类可以为我们的应用提供一个面向对象的封装;我们需要用到 Dispatch Queues 不具备的功能时,比如需要设置 operation 之间的依赖关系、使用 KVO 观察 operation 的状态变化等。下面的示例代码展示了创建一个 NSBlockOperation 对象的基本方法:@implementation&OQCreateBlockOperation
-&(NSBlockOperation&*)blockOperation&{
&&&&NSBlockOperation&*blockOperation&=&[NSBlockOperation&blockOperationWithBlock:^{
&&&&&&&&NSLog(@"Start&executing&block1,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&block1");
&&&&[blockOperation&addExecutionBlock:^{
&&&&&&&&NSLog(@"Start&executing&block2,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&block2");
&&&&[blockOperation&addExecutionBlock:^{
&&&&&&&&NSLog(@"Start&executing&block3,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&block3");
&&&&return&blockO
@end自定义 Operation 对象当系统预定义的两个子类 NSInvocationOperation 和 NSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。目前,我们可以自定义非并发和并发两种不同类型的 NSOperation 子类,而自定义一个前者要比后者简单得多。对于一个非并发的 operation ,我们需要做的就只是执行 main 方法中的任务以及能够正常响应取消事件就可以了,其它的复杂工作比如依赖配置、KVO 通知等 NSOperation 类都已经帮我们处理好了。而对于一个并发的 operation ,我们还需要重写 NSOperation 类中的一些现有方法。接下来,我们将会介绍如何自定义这两种不同类型的 NSOperation 子类。执行主任务从最低限度上来说,每一个 operation 都应该至少实现以下两个方法:一个自定义的初始化方法;main 方法。我们需要用一个自定义的初始化方法来将创建的 operation 置于一个已知的状态,并且重写 main 方法来执行我们的任务。当然,我们也可以实现一些其他的额外方法,比如实现 NSCoding 协议来允许我们归档和解档 operation 等。下面的示例代码展示了如何自定义一个简单的 operation :@interface&OQNonConcurrentOperation&()
@property&(strong,&nonatomic)&id&
@implementation&OQNonConcurrentOperation
-&(id)initWithData:(id)data&{
&&&&self&=&[super&init];
&&&&if&(self)&{
&&&&&&&&self.data&=&
&&&&return&
///&&不支持取消操作
-&(void)main&{
&&&&@try&{
&&&&&&&&NSLog(@"Start&executing&%@&with&data:&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&self.data,&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
&&&&@catch(NSException&*exception)&{
&&&&&&&&NSLog(@"Exception:&%@",&exception);
@end响应取消事件当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.通常来说,当我们自定义一个 operation 类时,我们需要考虑在以下几个关键点检查 isCancelled 方法的返回值:在真正开始执行任务之前;至少在每次循环中检查一次,而如果一次循环的时间本身就比较长的话,则需要检查得更加频繁;在任何相对来说比较容易中止 operation 的地方。看到这里,我想你应该可以意识到一点,那就是尽管 operation 是支持取消操作的,但却并不是立即取消的,而是在你调用了 operation 的 cancel 方法之后的下一个 isCancelled 的检查点取消的。///&&支持取消操作
-&(void)main&{
&&&&@try&{
&&&&&&&&if&(self.isCancelled)&
&&&&&&&&NSLog(@"Start&executing&%@&with&data:&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&self.data,&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&for&(NSUInteger&i&=&0;&i&<&3;&i++)&{
&&&&&&&&&&&&if&(self.isCancelled)&
&&&&&&&&&&&&sleep(1);
&&&&&&&&&&&&NSLog(@"Loop&%@",&@(i&+&1));
&&&&&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
&&&&@catch(NSException&*exception)&{
&&&&&&&&NSLog(@"Exception:&%@",&exception);
}配置并发执行的 Operation在默认情况下,operation 是同步执行的,也就是说在调用它的 start 方法的线程中执行它们的任务。而在 operation 和 operation queue 结合使用时,operation queue 可以为非并发的 operation 提供线程,因此,大部分的 operation 仍然可以异步执行。但是,如果你想要手动地执行一个 operation ,又想这个 operation 能够异步执行的话,你需要做一些额外的配置来让你的 operation 支持并发执行。下面列举了一些你可能需要重写的方法:start :必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现;main :可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰;isExecuting 和 isFinished :必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化;isConcurrent :必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。下面我们将分三部分内容来介绍一下定义一个并发执行的 operation 所需的基本代码,主体部分的代码如下所示:@implementation&OQConcurrentOperation
@synthesize&executing&=&_
@synthesize&finished&&=&_
-&(id)init&{
&&&&self&=&[super&init];
&&&&if&(self)&{
&&&&&&&&_executing&=&NO;
&&&&&&&&_finished&&=&NO;
&&&&return&
-&(BOOL)isConcurrent&{
&&&&return&YES;
-&(BOOL)isExecuting&{
&&&&return&_
-&(BOOL)isFinished&{
&&&&return&_
@end这一部分的代码看上去比较简单,但是却需要我们用心地去理解它。首先,我们用 @synthesize 关键字手动合成了两个实例变量 _executing 和 _finished ,然后分别在重写的 isExecuting 和 isFinished 方法中返回了这两个实例变量。另外,我们通过查看 NSOperation 类的头文件可以发现,executing 和 finished 属性都被声明成了只读的 readonly 。所以我们在 NSOperation 子类中就没有办法直接通过 setter 方法来自动触发 KVO 通知,这也是为什么我们需要在接下来的代码中手动触发 KVO 通知的原因。接下来是 start 方法的代码,在这个方法中,我们最需要关注的部分就是为 main 方法分离了一个新的线程,这是 operation 能够并发执行的关键所在。此外,在真正开始执行任务前,我们通过检查 isCancelled 方法的返回值来判断 operation 是否已经被 cancel ,如果是就直接返回了。-&(void)start&{&&&&
&&&&&&&&if&(self.isCancelled)&{
&&&&&&&&&&&&[self&willChangeValueForKey:@"isFinished"];
&&&&&&&&&&&&_finished&=&YES;
&&&&&&&&&&&&[self&didChangeValueForKey:@"isFinished"];
&&&&&&&&&&&&
&&&&&&&&[self&willChangeValueForKey:@"isExecuting"];
&&&&&&&&[NSThread&detachNewThreadSelector:@selector(main)&toTarget:self&withObject:nil];
&&&&&&&&_executing&=&YES;
&&&&&&&&[self&didChangeValueForKey:@"isExecuting"];
&&&&}最后,是真正执行任务的 main 方法,值得注意的是在任务执行完毕后,我们需要手动触动 isExecuting 和 isFinished 的 KVO 通知。-&(void)main&{
&&&&@try&{
&&&&&&&&NSLog(@"Start&executing&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&[self&willChangeValueForKey:@"isExecuting"];
&&&&&&&&_executing&=&NO;
&&&&&&&&[self&didChangeValueForKey:@"isExecuting"];
&&&&&&&&[self&willChangeValueForKey:@"isFinished"];
&&&&&&&&_finished&&=&YES;
&&&&&&&&[self&didChangeValueForKey:@"isFinished"];
&&&&&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
&&&&@catch&(NSException&*exception)&{
&&&&&&&&NSLog(@"Exception:&%@",&exception);
}注意,有一个非常重要的点需要引起我们的注意,那就是即使一个 operation 是被 cancel 掉了,我们仍然需要手动触发 isFinished 的 KVO 通知。因为当一个 operation 依赖其他 operation 时,它会观察所有其他 operation 的 isFinished 的值的变化,只有当它依赖的所有 operation 的 isFinished 的值为 YES 时,这个 operation 才能够开始执行。因此,如果一个我们自定义的 operation 被取消了但却没有手动触发 isFinished 的 KVO 通知的话,那么所有依赖它的 operation 都不会执行。维护 KVO 通知NSOperation 类的以下 key paths 支持 KVO 通知,我们可以通过观察这些 key paths 非常方便地监听到一个 operation 内部状态的变化:isCancelledisConcurrentisExecutingisFinishedisReadydependenciesqueuePrioritycompletionBlock与重写 main 方法不同的是,如果我们重写了 start 方法或者对 NSOperation 类做了大量定制的话,我们需要保证自定义的 operation 在这些 key paths 上仍然支持 KVO 通知。比如,当我们重写了 start 方法时,我们需要特别关注的是 isExecuting 和 isFinished 这两个 key paths ,因为这两个 key paths 最可能受重写 start 方法的影响。定制 Operation 对象的执行行为我们可以在创建一个 operation 后,添加到 operation queue 前,对 operation 的一些执行行为进行定制。下面介绍的所有定制均适用于所有的 operation ,与是否是自定义的 NSOperation 子类或系统预定义的 NSOperation 子类无关。配置依赖关系通过配置依赖关系,我们可以让不同的 operation 串行执行,正如我们前面提到的,一个 operation 只有在它依赖的所有 operation 都执行完成后才能开始执行。配置 operation 的依赖关系主要涉及到 NSOperation 类中的以下两个方法:-&(void)addDependency:(NSOperation&*)
-&(void)removeDependency:(NSOperation&*)顾名思义,第一个方法用于添加依赖,第二个方法则用于移除依赖。需要特别注意的是,用 addDependency: 方法添加的依赖关系是单向的,比如 [A addDependency:B]; ,表示 A 依赖 B,B 并不依赖 A 。另外,这里的依赖关系并不局限于相同 operation queue 中的 operation 之间。其实,从上面两个配置依赖关系的方法是存在于 NSOperation 类中的,我们也可以看出来,operation 的依赖关系是它自己管理的,与它被添加到哪个 operation queue 无关。因此,我们完全可以给一些 operation 配置好依赖关系,然后将它们添加到不同的 operation queue 中。但是,有一点是需要我们特别注意的,就是不要在 operation 之间添加循环依赖,因为这样会导致这些 operation 都不会被执行。注意,我们应该在手动执行一个 operation 或将它添加到 operation queue 前配置好依赖关系,因为在之后添加的依赖关系可能会失效。修改 Operation 在队列中的优先级对于被添加到 operation queue 中的 operation 来说,决定它们执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级。operation 的 isReady 状态取决于它的依赖关系,而在队列中的优先级则是 operation 本身的属性。默认情况下,所有新创建的 operation 的队列优先级都是 normal 的,但是我们可以根据需要通过 setQueuePriority: 方法来提高或降低 operation 的队列优先级。需要注意的是,队列优先级只应用于相同 operation queue 中的 operation 之间,不同 operation queue 中的 operation 不受此影响。另外,我们也需要清楚 operation 的队列优先级和依赖关系之间的区别。operation 的队列优先级只决定当前所有 isReady 状态为 YES 的 operation 的执行顺序。比如,在一个 operation queue 中,有一个高优先级和一个低优先级的 operation ,并且它们的 isReady 状态都为 YES ,那么高优先级的 operation 将会优先执行。而如果这个高优先级的 operation 的 isReady 状态为 NO ,而低优先级的 operation 的 isReady 状态为 YES 的话,那么这个低优先级的 operation 反而会优先执行。修改 Operation 执行任务线程的优先级从 iOS 4.0 开始,我们可以修改 operation 的执行任务线程的优先级。虽然 iOS 系统中的线程策略是由 kernel 内核管理的,但是一般来说,高优先级的线程相对于低优先级的线程来说能够得到更多的运行机会。我们可以给 operation 的线程优先级指定一个从 0.0 到 1.0 的浮点数值,0.0 表示最低的优先级,1.0 表示最高的优先级,默认值为 0.5 。注意,我们只能够在执行一个 operation 或将其添加到 operation queue 前,通过 operation 的 setThreadPriority: 方法来修改它的线程优先级。当 operation 开始执行时,NSOperation 类中默认的 start 方法会使用我们指定的值来修改当前线程的优先级。另外,我们指定的这个线程优先级只会影响 main 方法执行时所在线程的优先级。所有其它的代码,包括 operation 的 completion block 所在的线程会一直以默认的线程优先级执行。因此,当我们自定义一个并发的 operation 类时,我们也需要在 start 方法中根据指定的值自行修改线程的优先级。设置 Completion Block从 iOS 4.0 开始,一个 operation 可以在它的主任务执行完成时回调一个 completion block 。我们可以用 completion block 来执行一些主任务之外的工作,比如,我们可以用它来通知一些客户 operation 已经执行完毕,而并发的 operation 也可以用这个 block 来生成最终的 KVO 通知。如果需要设置一个 operation 的 completion block ,直接调用 NSOperation 类的 setCompletionBlock: 方法即可。注意,当一个 operation 被取消时,它的 completion block 仍然会执行,所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值。另外,我们也没有办法保证 completion block 被回调时一定是在主线程,理论上它应该是与触发 isFinished 的 KVO 通知所在的线程一致的,所以如果有必要的话我们可以在 completion block 中使用 GCD 来保证从主线程更新 UI 。执行 Operation 对象最终,我们需要执行 operation 来调度与其关联的任务。目前,主要有两种方式来执行一个 operation :将 operation 添加到一个 operation queue 中,让 operation queue 来帮我们自动执行;直接调用 start 方法手动执行 operation 。添加 Operation 到 Operation Queue 中就目前来说,将 operation 添加到 operation queue 中是最简单的执行 operation 的方式。另外,这里的 operation queue 指的就是 NSOperationQueue 类的一个具体实例。就技术上而言,我们可以在应用中创建任意数量的 operation queue ,但是 operation queue 的数量越多并不意味着我们就能同时执行越多的 operation 。因为同时并发的 operation 数量是由系统决定的,系统会根据当前可用的核心数以及负载情况动态地调整最大的并发 operation 数量。创建一个 operation queue 非常简单,跟创建其他普通对象没有任何区别:NSOperationQueue&*operationQueue&=&[[NSOperationQueue&alloc]&init];创建好 operation queue 后,我们可以使用下面三个方法添加 operation 到 operation queue 中:addOperation: ,添加一个 operation 到 operation queue 中;addOperations:waitUntilFinished: ,添加一组 operation 到 operation queue 中;addOperationWithBlock: ,直接添加一个 block 到 operation queue 中,而不用创建一个 NSBlockOperation 对象。在大多数情况下,一个 operation 被添加到 operation queue 后不久就会执行,但是也有很多原因会使 operation queue 延迟执行入队的 operation 。比如,我们前面提到了的,如果一个 operation 所依赖的其他 operation 还没有执行完成时,这个 operation 就不能开始执行;再比如说 operation queue 被暂停执行或者已经达到了它最大可并发的 operation 数。下面的示例代码展示了这三种方法的基本用法:@implementation&OQUseOperationQueue
-&(void)executeOperationUsingOperationQueue&{
&&&&NSOperationQueue&*operationQueue&=&[[NSOperationQueue&alloc]&init];
&&&&NSInvocationOperation&*invocationOperation&=&[[NSInvocationOperation&alloc]&initWithTarget:self&selector:@selector(taskMethod)&object:nil];
&&&&[operationQueue&addOperation:invocationOperation];
&&&&NSBlockOperation&*blockOperation1&=&[NSBlockOperation&blockOperationWithBlock:^{
&&&&&&&&NSLog(@"Start&executing&blockOperation1,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&blockOperation1");
&&&&NSBlockOperation&*blockOperation2&=&[NSBlockOperation&blockOperationWithBlock:^{
&&&&&&&&NSLog(@"Start&executing&blockOperation2,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&blockOperation2");
&&&&[operationQueue&addOperations:@[&blockOperation1,&blockOperation2&]&waitUntilFinished:NO];
&&&&[operationQueue&addOperationWithBlock:^{
&&&&&&&&NSLog(@"Start&executing&block,&mainThread:&%@,&currentThread:&%@",&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&&&&&sleep(3);
&&&&&&&&NSLog(@"Finish&executing&block");
&&&&[operationQueue&waitUntilAllOperationsAreFinished];
-&(void)taskMethod&{
&&&&NSLog(@"Start&executing&%@,&mainThread:&%@,&currentThread:&%@",&NSStringFromSelector(_cmd),&[NSThread&mainThread],&[NSThread&currentThread]);
&&&&sleep(3);
&&&&NSLog(@"Finish&executing&%@",&NSStringFromSelector(_cmd));
@end注意,在将一个 operation 添加到 operation queue 后就不要再修改这个 operation 了。因为 operation 被添加到 operation queue 后随时可能会执行,这个是由系统决定的,所以再修改它的依赖关系或者所包含的数据就很有可能会造成未知的影响。尽管 NSOperationQueue 类是被设计成用来并发执行 operation 的,但是我们也可以强制一个 operation queue 一次只执行一个 operation 。我们可以通过 setMaxConcurrentoperationCount: 方法来设置一个 operation queue 最大可并发的 operation 数,因此将这个值设置成 1 就可以实现让 operation queue 一次只执行一个 operation 的目的。但是需要注意的是,虽然这样可以让 operation queue 一次只执行一个 operation ,但是 operation 的执行顺序还是一样会受其他因素影响的,比如 operation 的 isReady 状态、operation 的队列优先级等。因此,一个串行的 operation queue 与一个串行的 dispatch queue 还是有本质区别的,因为 dispatch queue 的执行顺序一直是 FIFO 的。如果 operation 的执行顺序对我们来说非常重要,那么我们就应该在将 operation 添加到 operation queue 之前就建立好它的依赖关系。手动执行 Operation尽管使用 operation queue 是执行一个 operation 最方便的方式,但是我们也可以不用 operation queue 而选择手动地执行一个 operation 。从原理上来说,手动执行一个 operation 也是非常简单的,只需要调用它的 start 方法就可以了。但是从严格意义上来说,在调用 start 方法真正开始执行一个 operation 前,我们应该要做一些防范性的判断,比如检查 operation 的 isReady 状态是否为 YES ,这个取决于它所依赖的 operation 是否已经执行完成;又比如检查 operation 的 isCancelled 状态是否为 YES ,如果是,那么我们就根本不需要再花费不必要的开销去启动它。另外,我们应该一直通过 start 方法去手动执行一个 operation ,而不是 main 或其他的什么方法。因为默认的 start 方法会在真正开始执行任务前为我们做一些安全性的检查,比如检查 operation 是否已取消等。另外,正如我们前面说的,在默认的 start 方法中会生成一些必要的 KVO 通知,比如 isExcuting 和 isFinished ,而这些 KVO 通知正是 operation 能够正确处理好依赖关系的关键所在。更进一步说,如果我们需要实现的是一个并发的 operation ,我们也应该在启动 operation 前检查一下它的 isConcurrent 状态。如果它的 isConcurrent 状态为 NO ,那么我们就需要考虑一下是否可以在当前线程同步执行这个 operation ,或者是先为这个 operation 创建一个单独的线程,以供它异步执行。当然,如果你已经能够确定一个 operation 的可执行状态,那么你大可不必做这些略显啰嗦的防范性检查,直接调用 start 方法执行这个 operation 即可。下面的示例代码展示了手动执行一个 operation 的基本流程:@implementation&OQManualExecuteOperation
-&(BOOL)manualPerformOperation:(NSOperation&*)operation&{
&&&&BOOL&ranIt&=&NO;
&&&&if&(operation.isCancelled)&{
&&&&&&&&ranIt&=&YES;
&&&&}&else&if&(operation.isReady)&{
&&&&&&&&if&(!operation.isConcurrent)&{
&&&&&&&&&&&&[operation&start];
&&&&&&&&}&else&{
&&&&&&&&&&&&[NSThread&detachNewThreadSelector:@selector(start)&toTarget:operation&withObject:nil];
&&&&&&&&ranIt&=&YES;
&&&&return&ranIt;
@end取消 Operation从原则上来说,一旦一个 operation 被添加到 operation queue 后,这个 operation 的所有权就属于这个 operation queue 了,并且不能够被移除。唯一从 operation queue 中出队一个 operation 的方式就是调用它的 cancel 方法取消这个 operation ,或者直接调用 operation queue 的 cancelAllOperations 方法取消这个 operation queue 中所有的 operation 。另外,我们前面也提到了,当一个 operation 被取消后,这个 operation 的 isFinished 状态也会变成 YES ,这样处理的好处就是所有依赖它的 operation 能够接收到这个 KVO 通知,从而能够清除这个依赖关系正常执行。等待 Operation 执行完成一般来说,为了让我们的应用拥有最佳的性能,我们应该尽可能地异步执行所有的 operation ,从而让我们的应用在执行这些异步 operation 的同时还能够快速地响应用户事件。当然,我们也可以调用 NSOperation 类的 waitUntilFinished 方法来阻塞当前线程,直到这个 operation 执行完成。虽然这种方式可以让我们非常方便地处理 operation 的执行结果,但是却给我们的应用引入了更多的串行,限制了应用的并发性,从而降低了我们应用的响应性。注意,我们应该要坚决避免在主线程中去同步等待一个 operation 的执行结果,阻塞的方式只应该用在辅助线程或其他 operation 中。因为阻塞主线程会大大地降低我们应用的响应性,带来非常差的用户体验。除了等待一个单独的 operation 执行完成外,我们也可以通过调用 NSOperationQueue 的 waitUntilAlloperationsAreFinished 方法来等待 operation queue 中的所有 operation 执行完成。有一点需要特别注意的是,当我们在等待一个 operation queue 中的所有 operation 执行完成时,其他的线程仍然可以向这个 operation queue 中添加 operation ,从而延长我们的等待时间。暂停和恢复 Operation Queue如果我们想要暂停和恢复执行 operation queue 中的 operation ,可以通过调用 operation queue 的 setSuspended: 方法来实现这个目的。不过需要注意的是,暂停执行 operation queue 并不能使正在执行的 operation 暂停执行,而只是简单地暂停调度新的 operation 。另外,我们并不能单独地暂停执行一个 operation ,除非直接 cancel 掉。总结看到这里,我想你对 iOS 的并发编程模型已经有了一定的了解。正如文中所说的,我们应该尽可能地直接使用队列而不是线程,让系统去与线程打交道,而我们只需定义好要调度的任务就可以了。一般情况下,我们也完全不需要去自定义一个并发的 operation ,因为在与 operation queue 结合使用时,operation queue 会自动为非并发的 operation 创建一个线程。Operation Queues 是对 GCD 面向对象的封装,它可以高度定制化,对依赖关系、队列优先级和线程优先级等提供了很好的支持,是我们实现复杂任务调度时的不二之选。参考链接版权声明:我已将本文在微信公众平台的发表权「独家代理」给 iOS 开发(iOSDevTips)微信公众号。
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量14089点击量12236点击量10838点击量10088点击量9141点击量8592点击量7035点击量6346点击量5284
&2016 Chukong Technologies,Inc.
京公网安备89}

我要回帖

更多关于 nsinvocation 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信