//联系人:石虎QQ: 1224614774昵称:嗡嘛呢叭咪哄
1. 为什么优先使用NSOperationQueue而不是GCD
曾经我有一段时间我非常喜欢使用GCD来进行并发编程,因为虽然它是C的api,但是使用起来却非常简单和方便, 不过这样也就容易使开发者忘记并发编程中的许多注意事项和陷阱。
比如你可能写过类似这样的代码(这样来请求网络数据):
dispatch_async(_Queue, ^{//请求数据NSData*data = [NSDatadataWithContentURL:[NSURLURLWithString:@"http://domain.com/a.png"]];dispatch_async(dispatch_get_main_queue(), ^{ [selfrefreshViews:data]; });});
没错,它是可以正常的工作,但是有个致命的问题:这个任务是无法取消的。
dataWithContentURL:是同步的拉取数据,它会一直阻塞线程直到完成请求,如果是遇到了超时的情况,它在这个时间内会一直占有这个线程;在这个期间并发队列就需要为其他任务新建线程,这样可能导致性能下降等问题。
因此我们不推荐这种写法来从网络拉取数据。
操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。NSOperationQueue相对于GCD来说有以下优点:
提供了在 GCD 中不那么容易复制的有用特性。
可以很方便的取消一个NSOperation的执行
可以更容易的添加任务的依赖关系
提供了任务的状态:isExecuteing, isFinished.
名词: 本文中提到的 “任务”, “操作” 即代表要再NSOperation中执行的事情。
2. Operation Queues的使用
2.1 NSOperationQueue
NSOperationQueue有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用NSOperation的子类来表述。
NSOperationQueue*mainQueue = [NSOperationQueuemainQueue];//主队列NSOperationQueue*queue = [[NSOperationQueuealloc] init];//自定义队列NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{//任务执行}]; [queue addOperation:operation];
我们可以通过设置maxConcurrentOperationCount属性来控制并发任务的数量,当设置为 1时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和dispatch_queue_t是相似的。
2.2 NSOperation
你可以使用系统提供的一些现成的NSOperation的子类, 如NSBlockOperation、NSInvocationOperation等(如上例子)。你也可以实现自己的子类, 通过重写main或者start方法 来定义自己的operations。
使用main方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对重写 start 来说要少一些, 因为main方法执行完就认为operation结束了,所以一般可以用来执行同步任务。
@implementationYourOperation- (void)main{// 任务代码...}@end
如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写start方法, 但是注意:这种情况下,你必须手动管理操作的状态, 只有当发送 isFinished的 KVO 消息时,才认为是 operation 结束。
@implementationYourOperation- (void)start{self.isExecuting =YES;// 任务代码 ...}- (void)finish//异步回调{self.isExecuting =NO;self.isFinished =YES;}@end
当实现了start方法时,默认会执行start方法,而不执行main方法。为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO的方式进行实现。如果你不使用它们默认的 setter 来进行设置的话你就需要在合适的时候发送合适的 KVO消息。
需要手动管理的状态有:
isExecuting
代表任务正在执行中
isFinished
代表任务已经执行完成
isCancelled
代表任务已经取消执行
手动的发送 KVO消息, 通知状态更改如下 :
[self willChangeValueForKey:@"isCancelled"];_isCancelled=YES;[self didChangeValueForKey:@"isCancelled"];
为了能使用操作队列所提供的取消功能,你需要在长时间操作中时不时地检查 isCancelled属性, 比如在一个长的循环中:
@implementationMyOperation- (void)main{while(notDone && !self.isCancelled) {// 任务处理}}@end
按照以上说法,很多情况下我们都需要自定义operation来实现需求。
在这里,我参考了AFNetworking的底层实现
#import "AFURLConnectionOperation.h"
有兴趣的朋友可以深入研究一下
以下是我写的源码:(新鲜出炉)希望对朋友们有所帮助,喜欢就直接点儿
QWSSendOrderOperation.h文件
//// QWSSendOrderOperation.h// WEBluetooth//// Created by Tilink on 15/7/24.// Copyright (c) 2015年 Tilink. All rights reserved.//#importtypedefenum:NSUInteger{ orderStyleAuthentication =1,// 鉴权orderStyleWritePassword,// 写密码orderStyleLockOrUnLock,// 加解锁orderStyleOpenDevBarrel,// 开坐桶orderStyleLockEMachinery,// 锁定电机orderStyleSetLockEMCode,// 设置索电机码orderStyleFindDev,// 寻车orderStyleClearDis// 清除里程} OrderStytle;// 发送指令后的回调 0:OK 1:未知错误 2:鉴权失败typedefvoid(^ExecutingBlock)();typedefvoid(^CompleteBlock)(NSIntegererrCode,NSString* errDesc,iddata);@interfaceQWSSendOrderOperation:NSOperation// 发送数据@property(assign,nonatomic) OrderStytle orderStyle;@property(copy,nonatomic)NSString* orderContent;// 执行块@property(copy,nonatomic) ExecutingBlock executingBlock;// 请求完成后的回调@property(copy,nonatomic) CompleteBlock finishedBlock;/**
* @author ThinkerDown, 15-07-24 14:07:59
*
* @brief <#Description#>
*
* @param orderStyle <#orderStyle description#>
* @param orderContent <#orderContent description#>
* @param executingBlock <#executingBlock description#>
* @param finishedBlock <#finishedBlock description#>
*
* @return <#return value description#>
*/-(instancetype)initWithOrderStyle:(OrderStytle)orderStyle orderContent:(NSString*)orderContent executingBlock:(ExecutingBlock)executingBlock finished:(CompleteBlock)finishedBlock;///-------------------------------/// @name Accessing Run Loop Modes///-------------------------------/**
The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`.
*/@property(nonatomic,strong)NSSet*runLoopModes;// 完成- (void)finish;@end
QWSSendOrderOperation.m文件
//// QWSSendOrderOperation.m// WEBluetooth//// Created by Tilink on 15/7/24.// Copyright (c) 2015年 Tilink. All rights reserved.//#import"QWSSendOrderOperation.h"typedefNS_ENUM(NSInteger, QWSOperationState) { QWSOperationPausedState =-1, QWSOperationReadyState =1, QWSOperationExecutingState =2, QWSOperationFinishedState =3,};staticNSString*constkQWSSendOrderLockName =@"com.qws.sendorder.operation.lock";NSString*constQWSSendOrderOperationDidStartNotification =@"com.qws.sendorder.operation.start";NSString*constQWSSendOrderOperationDidFinishNotification =@"com.qws.sendorder.operation.finish";staticinlineNSString* QWSKeyPathFromOperationState(QWSOperationState state) {switch(state) {caseQWSOperationReadyState:return@"isReady";caseQWSOperationExecutingState:return@"isExecuting";caseQWSOperationFinishedState:return@"isFinished";caseQWSOperationPausedState:return@"isPaused";default: {#pragma clang diagnostic push#pragma clang diagnostic ignored"-Wunreachable-code"return@"state";#pragma clang diagnostic pop} }}staticinlineBOOLQWSStateTransitionIsValid(QWSOperationState fromState, QWSOperationState toState,BOOLisCancelled) {switch(fromState) {caseQWSOperationReadyState:switch(toState) {caseQWSOperationPausedState:caseQWSOperationExecutingState:returnYES;caseQWSOperationFinishedState:returnisCancelled;default:returnNO; }caseQWSOperationExecutingState:switch(toState) {caseQWSOperationPausedState:caseQWSOperationFinishedState:returnYES;default:returnNO; }caseQWSOperationFinishedState:returnNO;caseQWSOperationPausedState:returntoState == QWSOperationReadyState;default: {#pragma clang diagnostic push#pragma clang diagnostic ignored"-Wunreachable-code"switch(toState) {caseQWSOperationPausedState:caseQWSOperationReadyState:caseQWSOperationExecutingState:caseQWSOperationFinishedState:returnYES;default:returnNO; } }#pragma clang diagnostic pop}}@interfaceQWSSendOrderOperation()@property(readwrite,nonatomic,assign) QWSOperationState state;@property(readwrite,nonatomic,strong)NSRecursiveLock*lock;- (void)operationDidStart;- (void)finish;- (void)cancelConnection;@end@implementationQWSSendOrderOperation+ (void)networkRequestThreadEntryPoint:(id)__unused object {@autoreleasepool{ [[NSThreadcurrentThread] setName:@"QWSSendOrder"];NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop]; [runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}+ (NSThread*)networkRequestThread {staticNSThread*_networkRequestThread =nil;staticdispatch_once_toncePredicate;dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThreadalloc] initWithTarget:selfselector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; });return_networkRequestThread;}/**
* @author ThinkerDown, 15-07-24 14:07:59
*
* @brief <#Description#>
*
* @param orderStyle <#orderStyle description#>
* @param orderContent <#orderContent description#>
* @param executingBlock <#executingBlock description#>
* @param finishedBlock <#finishedBlock description#>
*
* @return <#return value description#>
*/-(instancetype)initWithOrderStyle:(OrderStytle)orderStyle orderContent:(NSString*)orderContent executingBlock:(ExecutingBlock)executingBlock finished:(CompleteBlock)finishedBlock {if(self= [superinit]) { _state = QWSOperationReadyState;self.lock = [[NSRecursiveLockalloc] init];self.lock.name = kQWSSendOrderLockName;self.orderStyle = orderStyle;self.orderContent = orderContent;self.runLoopModes = [NSSetsetWithObject:NSRunLoopCommonModes];self.executingBlock = executingBlock;self.finishedBlock = finishedBlock; }returnself;}#pragma mark -- (void)setState:(QWSOperationState)state {if(!QWSStateTransitionIsValid(self.state, state, [selfisCancelled])) {return; } [self.lock lock];NSString*oldStateKey = QWSKeyPathFromOperationState(self.state);NSString*newStateKey = QWSKeyPathFromOperationState(state); [selfwillChangeValueForKey:newStateKey]; [selfwillChangeValueForKey:oldStateKey]; _state = state; [selfdidChangeValueForKey:oldStateKey]; [selfdidChangeValueForKey:newStateKey]; [self.lock unlock];}- (void)pause {if([selfisPaused] || [selfisFinished] || [selfisCancelled]) {return; } [self.lock lock];if([selfisExecuting]) { [selfperformSelector:@selector(operationDidPause) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModes allObjects]];dispatch_async(dispatch_get_main_queue(), ^{NSNotificationCenter*notificationCenter = [NSNotificationCenterdefaultCenter]; [notificationCenter postNotificationName:QWSSendOrderOperationDidFinishNotification object:self]; }); }self.state = QWSOperationPausedState; [self.lock unlock];}- (void)operationDidPause { [self.lock lock];// 发送指令不能取消[self.lock unlock];}- (BOOL)isPaused {returnself.state == QWSOperationPausedState;}- (void)resume {if(![selfisPaused]) {return; } [self.lock lock];self.state = QWSOperationReadyState; [selfstart]; [self.lock unlock];}#pragma mark - NSOperation- (BOOL)isReady {returnself.state == QWSOperationReadyState && [superisReady];}- (BOOL)isExecuting {returnself.state == QWSOperationExecutingState;}- (BOOL)isFinished {returnself.state == QWSOperationFinishedState;}- (BOOL)isConcurrent {returnYES;}- (void)start { [self.lock lock];if([selfisCancelled]) { [selfperformSelector:@selector(cancelConnection) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModes allObjects]]; }elseif([selfisReady]) {self.state = QWSOperationExecutingState; [selfperformSelector:@selector(operationDidStart) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModes allObjects]]; } [self.lock unlock];}- (void)operationDidStart { [self.lock lock];if(![selfisCancelled]) {if(_executingBlock) {self.executingBlock(); } } [self.lock unlock];dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenterdefaultCenter] postNotificationName:QWSSendOrderOperationDidStartNotification object:self]; });}- (void)finish { [self.lock lock];self.state = QWSOperationFinishedState; [self.lock unlock];dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenterdefaultCenter] postNotificationName:QWSSendOrderOperationDidFinishNotification object:self]; });}- (void)cancel { [self.lock lock];if(![selfisFinished] && ![selfisCancelled]) { [supercancel];if([selfisExecuting]) { [selfperformSelector:@selector(cancelConnection) onThread:[[selfclass] networkRequestThread] withObject:nilwaitUntilDone:NOmodes:[self.runLoopModes allObjects]]; } } [self.lock unlock];}- (void)cancelConnection {if(![selfisFinished]) { [selffinish]; }}@end
谢谢!!!