Operation Queues

Cocoa Operations is an object-oriented method for encapsulating work to be performed asynchronously. Operations is designed to be used with operation Queue or alone. Because they are objective-C based, operations are most commonly used in Cocoa-based applications in OS X and iOS.

This chapter describes how to define and use operations.

About Operation Objects

The Operation object is an instance of the NSOperation class (in the Foundation Framework) that encapsulates the work you want your application to do. The NSOperation class itself is an abstract base class that must be subclassed to do any useful work. Although this class is abstract, it does provide a lot of infrastructure to minimize the amount of work you have to do in your own subclasses. In addition, the Foundation Framework provides two concrete subclasses that you can use in existing code. Table 2-1 lists these classes and a summary of how each class is used.

Table 2-1 Operation classes of the Foundation Framework

Class Description
NSInvocationOperation The classes used create operation objects based on objects and selectors in the application. This class can be used if you have existing methods that already perform the required tasks. Because it does not require subclassing, you can also use this class to create operation objects in a more dynamic manner. For information on how to use this class, see Creating an NSInvocationOperation Object below
NSBlockOperation As-is uses classes to execute one or more block objects simultaneously. Because a block Operation object can execute multiple blocks, it operates using group semantics; The operation itself is considered completed only when all relevant blocks have been completed. For information on how to use this class, see Creating an NSBlockOperation Object below. This class is available in OS X V10.6 and later. For more information about Blocks, see Blocks Programming Topics.
NSOperation Base class for defining custom Operation objects. Subclassing NSOperation gives you complete control over the implementation of your operations, including changing the default way operations are performed and reporting their status. For information on how to define a Custom Operation Object, see Defining a Custom Operation Object.

All operation objects support the following key functions:

  • Graph-based dependencies between operation objects are supported. These dependencies prevent a given operation from running before all operations on which it depends have finished running. For information about Configuring Dependencies, see Configuring Interoperation Dependencies.
  • Optional completion blocks are supported, which are executed after the main task of the operation completes. (OS X V10.6 and later only.) For information on Setting Up a Completion Block, see Setting Up a Completion Block.
  • Support for monitoring changes to operations execution status using KVO notifications. For information on how to observe KVO notifications, see the key-value Observing Programming Guide.
  • Support for prioritizing operations to affect their relative execution order. For more information, see Changing an Operation’s Execution Priority.
  • Support for cancel semantics that allow you to stop operations while they are executing. For information on Canceling Operations, see Canceling Operations. For information on how to support Cancellation in your own Operations, see Responding to Cancellation Events.

Operations is designed to help you increase the level of concurrency in your application. Operations is also a good way to organize and encapsulate the behavior of an application into simple discrete chunks. Instead of running some code on the main thread of your application, you can commit one or more operation objects to the queue and have the corresponding work executed asynchronously on one or more separate threads.

Concurrent Versus non-concurrent Operations

Although it is common to perform operations by adding operations to the Operation Queue, this is not required. You can also execute the operation object manually by calling its start method, but doing so does not guarantee that the operation will run at the same time as the rest of the code. The isConcurrent method of the NSOperation class tells you whether the operation is running synchronously or asynchronously relative to the thread calling its start method. By default, this method returns NO, which means that Operation runs synchronously in the calling thread.

If you want to implement a concurrent operation, that is, operations that run asynchronously relative to the calling thread, you must write additional code to start the operation asynchronously. For example, you could schedule a separate thread, call an asynchronous system function, or perform any other operations to ensure that the start method starts the task and returns immediately, probably before the task completes.

Most developers should not need to implement concurrent operation objects. If operations are always added to the Operation Queue, there is no need to implement concurrent operations. When a non-concurrent operation is submitted to an Operation Queue, the queue itself creates a thread to run the operation. Therefore, adding non-concurrent Operations to the Operation Queue still results in asynchronous execution of the operation object code. Concurrent operations need to be defined only if operations need to be performed asynchronously without adding them to the Operation Queue.

For information on how to create concurrent Operations, see Configuring Operations for Concurrent Execution in NSOperation Class Reference.

Creating an NSInvocationOperation Object

The NSInvocationOperation class is a concrete subclass of NSOperation that, at run time, invokes the selector specified on the specified object. Using this class avoids having to define a large number of custom Operation objects for each task in your application; This is especially true if you are modifying an existing application and already have the objects and methods you need to perform the necessary tasks. It can also be used if the method to be invoked is likely to change depending on the situation. For example, a call operation can be used to perform a selector that is dynamically selected based on user input.

The procedure for creating the Invocation operation is simple. Creates and initializes a new instance of the class, passing the required objects and selectors to be executed to the initializer method. Listing 2-1 shows two methods in a custom class that demonstrate the creation process. The taskWithData: method creates a new Invocation object and provides it with the name of another method that contains the task implementation.

Listing 2-1 Creating an NSInvocationOperation Object. Listing 2-1 Creating an NSInvocationOperation Object

@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
Copy the code

Creating an NSBlockOperation Object

The NSBlockOperation class is a concrete subclass of NSOperation that acts as a wrapper around one or more block objects. This class provides an object-oriented wrapper for applications that already use Operation Queues and do not want to create Dispatch queues at the same time. You can also use Block Operations to take advantage of other functions that may not be available in operation dependencies, KVO notifications, and Dispatch queues.

When creating a block operation, at least one block is usually added during initialization. You can add more blocks later as needed. When an NSBlockOperation object is executed, that object submits all its blocks to the default priority concurrency Dispatch queue. The object then waits for all blocks to execute. When the last block completes execution, the operation object marks itself as completed. Therefore, block operation can be used to track a group of executing blocks, just as thread joins are used to combine the results of multiple threads. The difference is that because the Block operation itself runs on a separate thread, other threads of the application can continue working while waiting for the block operation to complete.

Listing 2-2 shows a simple example of how to create an NSBlockOperation object. The block itself takes no arguments and returns no significant results.

Listing 2-2 Creating an NSBlockOperation Object

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

Once the Block Operation object is created, you can add more blocks to it using the addExecutionBlock: method. If blocks need to be executed sequentially, they must be submitted directly to the desired Dispatch queue.

Defining a Custom Operation Object

If the Block Operation and Invocation Operation objects do not fully meet the needs of the application, you can subclass NSOperation and add whatever behavior you need. The NSOperation class provides a general subclassing point for all operation objects. This class also provides a large infrastructure to handle most of the work required for dependencies and KVO notifications. However, there may be times when you need to supplement your existing infrastructure to keep your Operations running. The amount of extra work you need to do depends on whether you are implementing a non-concurrent operation or a concurrent operation.

Defining a non-concurrent operation is much simpler than defining a concurrent operation. For non-concurrent operations, all you have to do is execute the main task and respond to the cancellation event appropriately; The existing class Infrastructure does all the rest for you. For concurrent operations, some existing infrastructure must be replaced with custom code. The following sections show you how to implement both types of objects.

Performing the Main Task

Each operation object should implement at least the following methods:

  • A custom Initialization Method
  • main

You need a custom initialization method to put the operation object in a known state, and a custom main method to perform the task. Of course, you can implement other methods as needed, such as:

  • Do you plan to frommainA custom method called in the implementation of the
  • Accessor methods for setting data values and accessing operation results
  • NSCoding protocol methods that allow you to archive and unarchive operation objects

Listing 2-3 shows a startup template for a custom NSOperation subclass. This listing does not show how to handle cancellations, but it does show the methods that are commonly used. For information about handling cancellations, see Responding to Cancellation Events.) The initialization method of this class takes a single object as a data parameter and stores a reference to that object in the Operation object. The main method will act on the data object on the surface until the result is returned to the application.

Listing 2-3 Defining a simple operation object

@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
Copy the code

For a detailed example of how to implement an NSOperation subclass, see NSOperationSample.

Responding to cancel Events

After an operation is started, it continues to execute its task until it is complete or until your code explicitly cancels the operation. Cancellation can occur at any time, even before operation starts execution. Although the NSOperation class provides a method for clients to cancel operation, as required, It was voluntary to identify the cancellation event is voluntary by necessity. If an operation is terminated completely, there may be no way to reclaim allocated resources. Therefore, operation objects need to check for cancellation events and exit normally when they occur during an operation.

To support cancellation in operation objects, all you have to do is periodically call the object’s isCancelled method from your custom code, returning it immediately if it returns YES. Support for cancellation is important regardless of the duration of the operation, or whether you subclass NSOperation directly or use one of its specific subclasses. The isCancelled method itself is very lightweight and can be called frequently without incurring any noticeable performance penalty. When designing operation objects, consider calling the isCancelled method at the following locations in your code:

  • Before performing any actual work
  • At least once in each iteration of the loop, or more often if each iteration is relatively long
  • Anywhere in the code where it is relatively easy to abort an operation

Listing 2-4 provides a very simple example of how to respond to a cancellation event in the main method of an Operation object. In this case, the isCancelled method is called each time through the while loop, allowing for a quick exit before work begins and to be called again periodically.

Listing 2-4 Responding to a Cancellation Request

- (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.}}Copy the code

Although the previous example does not include cleaning code, your own code should ensure that all resources allocated by your custom code are freed.

Configuring Operations for Concurrent Execution

By default, Operation objects execute synchronously, that is, they perform tasks in the thread that calls their start method. However, because Operation Queues provide threads for non-concurrent operations, most operations still run asynchronously. However, if you plan to execute operations manually but still want them to run asynchronously, you must take appropriate actions to ensure that they run asynchronously. You can do this by defining the Operations object as concurrent Operations.

Table 2-2 lists the methods that are commonly overridden to implement concurrent operations.

Table 2-2 Methods to Override for Concurrent operations

Method Description
start All concurrent Operations must override this method and replace the default implementation with their own custom implementation. To perform the operation manually, call its start method. Thus, the implementation of this method is the starting point for the action and is where the thread or other execution environment to execute the task is set up. Your implementation should not call super at any time.
main (Optional) This method is typically used to implement tasks associated with operations objects. Although you can perform tasks in the start method, implementing tasks using this method allows for a clearer separation of setup code and task code.
isExecuting isFinished (Required) Concurrent Operations is responsible for setting up its execution environment and reporting the status of that environment to external clients. Therefore, concurrent Operations must maintain some state information to know when it is performing tasks and when it is finished. It must then report that state using these methods. The implementation of these methods must be able to safely call simultaneously from other threads. When changing the values reported by these methods, you must also generate the appropriate KVO notifications for the expected key path.
isConcurrent Required To identify operations as a concurrent operation, override this method and return YES.

The rest of this section shows a sample implementation of the MyOperation class, which demonstrates the basic code needed to implement concurrent operations. The MyOperation class simply executes its own main method on the separate thread it creates. The actual work performed by the main method is irrelevant. The focus of the example is to demonstrate the infrastructure that needs to be provided when defining concurrent operations.

Listing 2-5 shows the interface and partial implementation of the MyOperation class. The isConcurrent, isExecuting, and isFinished methods of the MyOperation class are relatively simple to implement. The isConcurrent method should simply return YES to indicate that this is a concurrent operation. The isExecuting and isFinished methods only return values stored in the instance variables of the class itself.

Listing 2-5 Defining a concurrent operation

@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
Copy the code

Listing 2-6 shows the start method for MyOperation. The implementation of this approach is minimal in order to demonstrate tasks that you absolutely must perform. In this case, the method simply starts a new thread and configates it to call the main method. The method also updates the execute member variable and generates a KVO notification for isExecuting Key PATH to reflect changes in that value. When the work is done, the method returns and lets the newly detached (newly opened) thread perform the actual task.

Listing 2-6 The 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"];
}
Copy the code

Listing 2-7 shows the remaining implementation of the MyOperation class. As shown in Listing 2-6, the main method is the entry point for the new thread. It performs the work associated with the operation object and calls the custom completeOperation method when the work is finally completed. The completeOperation method then generates the required KVO notifications for isExecuting and isFinished Key Path to reflect the change in operation state.

Listing 2-7 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"];
}
Copy the code

Even if Operation is cancelled, you should always notify the KVO observer that your operation has now completed its work. When an operation object depends on the completion of other operation objects, it monitors the isFinished Key path of those objects. The dependency on Operation signals that it is ready to run only when all objects report that they are complete. Therefore, failure to generate completion notifications may prevent other Operations from being performed in the application.

Maintaining KVO Compliance

The NSOperation class is compatible with key-value Observing (KVO) for the following key paths:

  • isCancelled
  • isConcurrent
  • isExecuting
  • isFinished
  • isReady
  • dependencies
  • queuePriority
  • completionBlock

If you override the start method or do any significant customization of the NSOperation object (as opposed to overriding main), you must make sure that your custom objects remain KVO compatible for these key Paths. When overriding the start method, the key paths you should pay attention to are isExecuting and isFinished. These are the most common key Paths for reimplementing the method.

If you want to implement support for dependencies on objects other than the other operation objects, you can also override the isReady method and force it to return NO until the custom dependencies are satisfied. (If you implement custom dependencies, be sure to call super from the isReady method if the default dependency management system provided by the NSOperation class is still supported.) When the ready state of the Operation object changes, KVO notifications are generated for the isReady Key Path to report these changes. Unless you override the addDependency: or removeDependency: methods, you don’t have to worry about generating KVO notifications for the dependency Key Path.

Although you can generate KVO notifications for other key paths in NSOperation, you are unlikely to need to do so. If you need to cancel an operation, you simply call the existing Cancel method. Similarly, there is no need to modify the queue priority information in the Operation object. Finally, there is no need to provide KVO notifications for isConcurrent Key Path unless operation is able to dynamically change its concurrency state.

For more information on Key Value Observing and how to support it in custom objects, see the Key-value Observing Programming Guide.

Customizing the Execution Behavior of an Operation Object

The configuration of Operation objects occurs after they are created, but before they are added to the queue. The configuration types described in this section apply to all Operation objects, whether you subclass NSOperation yourself or use an existing subclass.

Configuring Interoperation Dependencies

Dependencies are a way to serialize the execution of different Operation objects. Operations that depend on other operations cannot start executing until all operations they depend on have finished executing. Therefore, you can use dependencies to create simple one-to-one dependencies between two Operation objects, or to build complex object dependency diagrams.

To establish a dependency between two Operation objects, use the addDependency: method of NSOperation. This method creates a one-way dependency from the current operation object to the target operation specified as a parameter. This dependency means that the current object cannot begin execution until the target object completes execution. Dependencies are also not limited to operations in the same queue. Operation objects manage their own dependencies, so it is perfectly acceptable to create dependencies between operations and add them all to different queues. However, one thing that is not acceptable is to create circular dependencies between operations. Doing so is a programmer error and will prevent the affected operation from running.

An operation object is usually ready to execute when all of its dependencies have been executed. (If you customize the behavior of the isReady method, the state of readiness for operation is determined by the condition set.) If the operation object is in a queue, the queue can start executing the operation at any time. If you plan to execute the operation manually, it is up to you to call the start method of the operation.

Important: Dependencies should always be configured before running operations or adding them to the Operation Queue. Dependencies added later may not prevent a given operation object from running.

Dependencies depend on each Operation object to send appropriate KVO notifications when the object state changes. If you customize the behavior of the Operation object, you may need to generate appropriate KVO notifications from your custom code to avoid creating dependency problems. For more information about KVO notifications and operation objects, see Maintaining KVO Compliance. For more information about configuring dependencies, see NSOperation Class Reference.

Changing an Operation’s Execution Priority

For operations added to the queue, the order of execution is determined first by the ready state of the queued operations, and then by their relative priority. Readiness is determined by operation’s dependencies on other operations, but the priority is an attribute of the Operation object itself. By default, all new operation objects have a “normal” priority, but you can increase or decrease this priority as needed by calling the object’s setQueuePriority: method.

The priority applies only to operations in the same operation queue. If your application has multiple queues of operations, each queue prioritizes its own operations independently of any other queues. Therefore, low-priority operations can still be executed before high-priority operations in different queues.

Priorities are no substitute for dependencies. The priority determines the order in which the operation queue starts executing only the currently ready operations. For example, if the queue contains both high-priority and low-priority operations, and both Operations are ready, the queue executes the high-priority operation first. However, if a higher-priority operation is not ready and a lower-priority operation is ready, the queue executes the lower-priority operation first. If you want to prevent one operation from starting before another operation completes, you must use a dependency instead (as described in configuring interop dependencies).

Changing the Underlying Thread Priority

In OS X V10.6 and later, you can configure the execution priority of the underlying thread of operation. The threading policy in the system itself is managed by the kernel, but generally high-priority threads have more opportunities to run than low-priority threads. In the Operation object, specify thread priority as a floating point value between 0.0 and 1.0, where 0.0 is the lowest priority and 1.0 is the highest priority. If no explicit thread priority is specified, operation runs with the default thread priority of 0.5.

To set the thread priority for Operation, you must first call the setThreadPriority: method of the operation object and then add it to the queue (or do it manually). During operation, the default start method uses the value you specify to change the priority of the current thread. This new priority is only valid during main of operation. All other code (including the completion block for Operation) runs at the default thread priority. If you create a concurrent operation and therefore override the start method, you must configure the thread priority yourself.

Setting Up a Completion Block

In OS X V10.6 and later, an operation can execute a completion block when its main task completes execution. You can use completion blocks to perform any work that is not part of the main task. For example, you can use this block to inform the interested Clients operation that it has itself completed. Concurrent operation objects may use this completion block to generate their final KVO notifications.

To set up a complete block, use the setCompletionBlock: method of NSOperation. Blocks passed to this method should have no arguments and return values.

Tips for Implementing Operation Objects

Although operation objects are easy to implement, there are a few things you should be aware of when writing code. The following sections describe the factors to consider when writing code for operation objects.

Managing Memory in Operation Objects

The following sections describe the key elements of good memory management in operation objects. For general information about Memory Management in Objective-C programs, see Advanced Memory Management Programming Guide.

Avoid per-thread Storage

Although most operations are performed on a thread, in the case of non-concurrent operations, that thread is typically provided by an Operation Queue. If an operation queue provides you with a thread, you should consider that thread to belong to the queue and not be touched by your operation. In particular, you should not associate any data with threads that you do not create or manage yourself. The comings and goings of threads managed by action queues depend on the needs of the system and application. Therefore, using per-Thread Storage to pass data between operations is unreliable and is likely to fail.

Per-thread storage should not be used for operation objects under any circumstances. When you initialize an Operation object, you should provide the object with everything it needs to perform its task. Therefore, the Operation object itself provides the required context storage. All incoming and outgoing data should be stored there until it can be reintegrated into the application or is no longer needed.

Keep References to Your Operation Object As Needed

Just because operation objects run asynchronously, you should not assume you can create them and ignore them. They are still just objects, and you manage any references to them that your code needs. This is especially important if you need to retrieve result data from the operation after it has completed.

The reason you should always keep your own reference to Operation is that you may not have the opportunity to request an object from the queue later. Queues make every effort to schedule and execute operations as quickly as possible. In many cases, queues start performing operations almost immediately after they are added. By the time your own code returns to the queue to get a reference to the operation, the operation may have completed and been removed from the queue.

Handling Errors and Exceptions

Because operations are essentially discrete entities in the application, they are responsible for handling any errors or exceptions that occur. In OS X V10.6 and later, the default start method provided by the NSOperation class does not catch exceptions. (In OS X V10.5, the start method does catch and suppress exceptions.) Your own code should always catch and suppress exceptions directly. It should also check for error code and notify the appropriate parts of the application as needed. If you replace the start method, you must also catch any exceptions in your custom implementation to prevent them from leaving the scope of the underlying thread.

You should be prepared to handle the following types of error situations:

  • Check and handle UNIX errno style error code.
  • Check for explicit error codes returned by methods and functions.
  • Catch exceptions thrown by your own code or other system frameworks.
  • Catch an exception thrown by the NSOperation class itself, which throws an exception in the following cases:
    • Called when operation is not ready to executestartmethods
    • Call operation again when it is executing or completing (perhaps because it was canceled)startmethods
    • When you try to add a completion block to an executed or completed operation
    • When you try to retrieve the result of a canceled NSInvocationOperation object

If your custom code does encounter an exception or error, you should take whatever steps are necessary to propagate the error to the rest of the application. The NSOperation class does not provide an explicit way to pass error result code or exceptions to other parts of the application. Therefore, if this information is important to the application, the necessary code must be provided.

Determining an Appropriate Scope for Operation Objects

Although you can add any number of Operations to the Operation Queue, it is usually impractical to do so. Like any object, instances of the NSOperation class consume memory and have real costs associated with their execution. If each of your Operation objects does a small amount of work, and you create tens of thousands of Operations objects, you may find that scheduling the operation takes more time than it actually does. If your application is already memory constrained, you may find that simply having thousands of Operation objects in memory can further degrade performance.

The key to using Operations effectively is to find the right balance between the amount of work you need to do and keeping your computer busy. Try to make sure your work is done reasonably. For example, if your application creates 100 Operation objects to perform the same task on 100 different values, consider creating 10 Operation objects to handle 10 values each.

You should also avoid adding a large number of operations to the queue at once, or continuously adding operation objects to the queue faster than you can process them. Instead of flooding the queue with operation objects, create them in batches. When a batch of processing completes execution, the completion block is used to inform the application to create a batch of new processing. When you have a lot of work to do, you want to fill the queue with enough operations to keep the machine busy, but you don’t want to run out of memory by creating too many operations at once.

Of course, the number of Operation objects you create and the amount of work performed within each operation object is variable and entirely dependent on your application. You should always use tools such as Instruments to help you find the right balance between efficiency and speed. For an Overview of the tools and other Performance tools you can use to collect code metrics, see Performance Overview.

Executing Operations

Finally, the application needs to perform operations to do its job. In this section, you will learn several ways to execute operation and how to manipulate its execution at runtime.

Adding Operations to an Operation Queue

By far the simplest way to perform operation is to use an operation queue, which is an instance of the NSOperationQueue class. The application is responsible for creating and maintaining any action queues it intends to use. An application can have any number of queues, but there is a practical limit to the number of operations that can be performed at a given point in time. Operation queues work with the system to limit the number of concurrent operations to a value that fits the available kernel and system load. Therefore, creating additional queues does not mean you can perform additional operations.

To create a queue, you can allocate it in your application like any other object:

NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
Copy the code

To add operations to the queue, use the addOperation: method. In OS X v10.6 and higher versions, you can use addOperations: waitUntilFinished: method, also can use addOperationWithBlock: Method adds a block object directly to the queue (without the corresponding operation object). Each of these methods queues one or more operations and informs the queue that it should start processing them. In most cases, operation is executed shortly after it is added to the queue, but the operation queue may delay the execution of the queue operation for any of the following reasons. Specifically, if the queued operations depend on other operations that have not yet completed, execution may be delayed. Execution may also be delayed if the action queue itself is suspended or is already executing its maximum concurrent operands. The following example shows the basic syntax for adding Operations to a queue.

[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
   /* Do something. */
}];
Copy the code

Important: Make all necessary configuration and modifications to the Operation object before adding it to the queue, because once added, the operation may be running at any time, which may be too late for the desired effect of the change.

Although the NSOperationQueue class is designed to execute operations concurrently, you can force a single queue to run only one operation at a time. SetMaxConcurrentOperationCount: method allows you to maximum number of concurrent operation operation queue object configuration. Passing the value 1 to this method causes the queue to perform only one operation at a time. Although only one operation can be executed at a time, the order of execution is still based on other factors, such as the readiness of each operation and the priority assigned to it. Thus, the serialized operation queue provides different behavior than the serial Dispatch queue in Grand Central Dispatch. If the order in which operation objects are executed is important to you, you should use dependencies to establish that order before adding operations to the queue. For Configuring Dependencies, see Configuring Interoperation Dependencies.

For information about using operation queues, see NSOperationQueue Class Reference. For more information about Serial Dispatch Queues, see Creating Serial Dispatch Queues.

Executing Operations Manually

Although operation queues are the most convenient way to run operation objects, operation can also be performed without queues. However, if you choose to execute the operation manually, you should take some precautions in your code. In particular, Operation must be ready to run, and it must always be started with its start method.

Operation is considered unable to run until the isReady method returns YES. The isReady method is integrated into the dependency management system of the NSOperation class to provide the state of the Operation dependencies. Operation is free to start executing only when its dependencies are cleared (after all execution is complete).

When you manually execute operation, always use the start method to start execution. Use this method rather than main or some other method, because the start method performs some security checks before actually running the custom code. In particular, the default start method generates the KVO notifications that Operation needs to properly handle its dependencies. This method also correctly avoids executing the operation if it has been canceled; If operation is not actually ready to run, an exception is thrown.

If your application defines concurrent operation objects, you should also consider calling the isConcurrent operation methods before starting them. If this method returns NO, the local code can decide whether to execute operation synchronously in the current thread or to create a separate thread first. However, implementing this check is entirely up to you.

Listing 2-8 shows a simple example of what checks should be performed before performing operation manually. If the method returns NO, you can schedule a timer and call the method again later. It then continues to rearrange the timer until the method returns YES, possibly because Operation was canceled.

Listing 2-8 Executing an operation object Manually

- (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;
}
Copy the code

Canceling Canceling Operations.

Once added to the operation queue, the operation object is actually owned by the queue and cannot be deleted. The only way to get Operation out of the queue is to cancel operation. You can cancel a single operation object by calling its cancel method, or you can cancel all operation objects in the queue by calling the cancelAllOperations method on the queue object.

Operations should be cancelled only if it is determined that they are no longer needed. Issuing the cancel command puts the operation object in a “canceled” state, which prevents it from running. Since the cancelled operation is still considered “finished”, objects that depend on it will receive the appropriate KVO notification to clear the dependency. Therefore, it is more common to cancel all queued operations in response to some important event (such as an application exit or a user specific request to cancel) than to cancel operations selectively.

Waiting for Operations to Finish

For best performance, you should design the operation as asynchronous as possible, leaving the application free to do other work while the operation is executing. If the code that created the operation also processes the results of that operation, you can use the waitUntilFinished method of NSOperation to block the code until the operation is complete. However, in general, it’s best to avoid calling this method if you can. Blocking the current thread may be a convenient solution, but it does introduce more serialization into the code and limit the overall concurrency.

Important: Never wait for an action from the main thread of the application. You can only do this from a worker thread or other operation. Blocking the main thread prevents the application from responding to user events and can make the application appear unresponsive.

In addition to wait for a single operation to complete, can also by calling NSOperationQueue waitUntillalOperationsRefished method to wait for all the operations in the queue. While waiting for the entire queue to complete, note that other threads of the application can still add Operations to the queue to prolong the wait time.

Suspending and supporting Queues

If you want to temporarily stop the execution of operations, you can use the setSuspended: method to suspend the corresponding operation queue. Suspending the queue does not cause operations that are already running to be suspended during the execution of the task. It simply prevents new operations from being scheduled. You can suspend any work in progress by suspending the queue in response to a user request in anticipation that the user might eventually want to resume the work.

Refer to the link

Reference link :🔗

  • NSOperation
  • NSBlockOperation
  • NSInvocationOperation
  • NSOperationQueue
  • Operation Queues
  • IOS multithreading: summary of NSOperation, NSOperationQueue
  • Concurrent programming: apis and challenges