Operation Queues
NSInvocationOperation
@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data {
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTaskMethod:) object:data];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
NSBlockOperation
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
After creating a block operation object, you can add more blocks to it using the addExecutionBlock: method. If you need to execute blocks serially, you must submit them directly to the desired dispatch queue.
Defining a Custom Operation Object
For a concurrent operation, you must replace some of the existing infrastructure with your custom code.
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
if (self = [super init])
myData = data;
return self;
}
-(void)main {
@try {
// Do some work on myData and report the results.
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
Responding to Cancellation Events
To support cancellation in an operation object, all you have to do is call the object’s isCancelled method periodically from your custom code and return immediately if it ever returns YES.
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
main method
Although the preceding example contains no cleanup code, your own code should be sure to free up any resources that were allocated by your custom code.
Configuring Operations for Concurrent Execution
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
- (id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
@end
start method
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
Updating an operation at completion time
- (void)main {
@try {
// Do the main work of the operation here.
[self completeOperation];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
Maintaining KVO Compliance
The NSOperation class is key-value observing (KVO) compliant for the following key paths:
- isCancelled
- isConcurrent
- isExecuting
- isFinished
- isReady
- dependencies
- queuePriority
- completionBlock
When overriding the start method, the key paths you should be most concerned with are isExecuting and isFinished.
dependency
If you want to implement support for dependencies on something besides other operation objects, you can also override the isReady method and force it to return NO until your custom dependencies were satisfied.
Customizing the Execution Behavior of an Operation Object
Configuring Interoperation Dependencies
This dependency means that the current object cannot begin executing until the target object finishes executing. Dependencies are also not limited to operations in the same queue.
Changing an Operation’s Execution Priority
Priority levels are not a substitute for dependencies. Priorities determine the order in which an operation queue starts executing only those operations that are currently ready. For example, if a queue contains both a high-priority and low-priority operation and both operations are ready, the queue executes the high-priority operation first. However, if the high-priority operation is not ready but the low-priority operation is, the queue executes the low-priority operation first. If you want to prevent one operation from starting until another operation has finished, you must use dependencies (as described in Configuring Interoperation Dependencies) instead.
优先级是对ready状态的同一队列有效,如果有依赖,优先执行依赖的规则
Changing the Underlying Thread Priority
- setThreadPriority:
0~1.0
Setting Up a Completion Block
To set a completion block, use the
setCompletionBlock:
method of NSOperation. The block you pass to this method should have no arguments and no return value.
Executing Operations
Adding Operations to an Operation Queue
Operation queues work with the system to restrict the number of concurrent operations to a value that is appropriate for the available cores and system load. Therefore, creating additional queues does not mean that you can execute additional operations.
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
加入queue的延迟时间
To add operations to a queue, you use the addOperation: method. In OS X v10.6 and later, you can add groups of operations using the addOperations:waitUntilFinished: method, or you can add block objects directly to a queue (without a corresponding operation object) using the addOperationWithBlock: method. Each of these methods queues up an operation (or operations) and notifies the queue that it should begin processing them. In most cases, operations are executed shortly after being added to a queue, but the operation queue may delay execution of queued operations for any of several reasons. Specifically, execution may be delayed if queued operations are dependent on other operations that have not yet completed. Execution may also be delayed if the operation queue itself is suspended or is already executing its maximum number of concurrent operations. The following examples show the basic syntax for adding operations to a queue.
- setMaxConcurrentOperationCount:
设置1的话每次执行一个任务,Although only one operation at a time may execute, the order of execution is still based on other factors, such as the readiness (准备状态) of each operation and its assigned priority. Thus, a serialized operation queue does not offer quite the same behavior as a serial dispatch queue in Grand Central Dispatch does
Executing Operations Manually
you must always start it using its start method.
An operation is not considered able to run until its isReady method returns YES. The isReady method is integrated into the dependency management system of the NSOperation class to provide the status of the operation’s dependencies. Only when its dependencies are cleared is an operation free to begin executing.
When executing an operation manually, you should always use the start method to begin execution. You use this method, instead of main or some other method, because the start method performs several safety checks before it actually runs your custom code. In particular, the default start method generates the KVO notifications that operations require to process their dependencies correctly. This method also correctly avoids executing your operation if it has already been canceled and throws an exception if your operation is not actually ready to run.
使用start开启任务执行而不是main,start会执行安全检查和生成kvo通知
-
if your application defines concurrent operation objects, you should also consider calling the
isConcurrent method
of operations prior to launching them. In cases where this method returns NO, your local code can decide whether to execute the operation synchronously in the current thread or create a separate thread first. However, implementing this kind of checking is entirely up to you.
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
Canceling Operations
Once added to an operation queue, an operation object is effectively owned by the queue and cannot be removed. The only way to dequeue an operation is to cancel it. You can cancel a single individual operation object by calling its cancel method or you can cancel all of the operation objects in a queue by calling the cancelAllOperations method of the queue object.
You should cancel operations only when you are sure you no longer need them. Issuing a cancel command puts the operation object into the “canceled” state, which prevents it from ever being run. Because a canceled operation is still considered to be “finished”, objects that are dependent on it receive the appropriate KVO notifications to clear that dependency. Thus, it is more common to cancel all queued operations in response to some significant event, like the application quitting or the user specifically requesting the cancellation, rather than cancel operations selectively.
cancel也会促发kvo,cancel、
状态等同于finish,会让依赖清除
Waiting for Operations to Finish
- waitUntilFinished
- waitUntilAllOperationsAreFinished
Suspending and Resuming Queues
- setSuspended:
只对队列中没有执行的operation有效