Brief Intro to Operations and Operation Queues of Foundation Framework
2013-11-27 18:12
555 查看
Operations and Operation Queues
In Chapter 11, you learned about operationobjects, instances of the NSOperation class
(and its subclasses) that encapsulate the code and data for a single task. As an operation object encapsulates a single unit of work, it is an ideal vehicle for implementing concurrent programming. The Foundation Framework includes the following
three operation classes:
NSOperation:
The base (abstract) class for defining operation objects. For nonconcurrent tasks, a concrete subclass typically only needs to override themain method. For
concurrent tasks, you must override at a minimum the methods start, isConcurrent, isExecuting,
and isFinished.
NSBlockOperation:
A concrete subclass of NSOperation that is used to execute one or more block objects
concurrently. An NSBlockOperationobject is considered finished only when all of its block objects have finished execution.
NSInvocationOperation:
A concrete subclass of NSOperation that is used to create an operation object based on an object and selector that you specify.
The following statement creates an NSBlockOperation instance named greetingOp.
NSBlockOperation* greetingOp = [NSBlockOperation blockOperationWithBlock: ^{ NSLog(@"Hello, World!"); }];
You can also add additional blocks to an NSBlockOperation instance using theaddExecutionBlock: method.
The following statement adds a block to theNSBlockOperation instance greetingOp.
[greetingOp addExecutionBlock: ^{ NSLog(@"Goodbye"); }];
An NSInvocationOperation can be created and initialized using either an NSInvocationobject
or a selector and receiver object. The following statement creates anNSInvocationOperation with the selector hello and
a receiver object named greetingObj.
NSInvocationOperation invokeOp = [[NSInvocationOperation alloc] initWithTarget:greetingObj selector:@selector(hello)];
You can also implement custom operation classes. A custom operation class subclassesNSOperation and
must implement, at a minimum, the main method to perform the desired task. Optionally, it can also provide the following functionality:
Custom initialization methods
Custom helper methods (invoked via the main method)
Accessor methods for setting data values and accessing the results of the operation
Methods that conform the class to the NSCoding protocol (to support archiving the object)
Operation objects support a variety of features that facilitate concurrent programming, several of which are
Establishing dependencies between operation objects, thereby enabling you to control their order of execution.
Creating a completion block that is executed after an operation’s main task has finished.
Retrieving an operation’s execution state.
Prioritizing operations in an operation queue.
Cancelling operations.
An operation object is executed by invoking its start method.
The default implementation of this method executes the operation’s task (implemented by its main method) synchronously. Hence, you may be wondering how operation
objects support concurrent programming. Well, operation objects are typically executed by adding them to operation queues, which provide built-in support for executing
operations concurrently. Specifically, operation queues provide threads for executing operations.
An operation queue is a mechanism that provides the capability to execute operations concurrently. The Foundation Framework NSOperationQueue class
is an Objective-C implementation of an operation queue. An operation can be added to an NSOperationQueueinstance as a block object or an instance of a subclass of NSOperation.
An operation queue manages the execution of operations. Thus it includes methods to manage operations in the queue, manage the number of running operations, suspend operations, and retrieve specific queues. Listing
17-16 creates and initializes an NSOperationQueue instance and then uses itsaddOperationWithBlock: method
to submit a block object to the queue.
Listing
17-16. Adding a Block Object to an Operation Queue
NSOperationQueue *queue = [NSOperationQueue new]; [queue addOperationWithBlock: ^{ NSLog(@"Hello, World!"); }]; [queue waitUntilAllOperationsAreFinished];
Once an operation is added to a queue, it remains in the queue until it is explicitly cancelled or finishes executing its task. You can cancel an (NSOperation)
object added to an operation queue by invoking its cancel method or by invoking the cancelAllOperations method
on the queue.
The execution order of operations within a queue is a function of the priority level of each operation and the interoperation object dependencies. The current implementation ofNSOperationQueue uses
Grand Central Dispatch to initiate execution of their operations. As a result, each operation in the queue is executed in a separate thread.
Operation objects and operation queues provide an object-oriented mechanism for performing asynchronous, concurrent programming. They eliminate the need for low-level thread management, and simplify synchronization and coordination
of execution for multiple interdependent tasks. Because they utilize system services that can scale dynamically in response to resource availability and utilization, they ensure that tasks are executed as quickly and as efficiently as possible.
Executing Operation Objects Manually
Although operation objects are typically executed using operation queues, it is possible to start an operation object manually (i.e., not add it to a queue). To do this, you must code the operation as a concurrent operation in
order to have it execute it asynchronously. This is accomplished by performing the following steps:
Override the start method. This method should be updated to execute the operation asynchronously, typically by invoking its main method
in a new thread.
Override the main method (optional). In this method, you implement the task associated with the operation. If preferred, you can skip this method and implement the task
in the start method, but overriding the main method provides a cleaner design
that is consistent with the intent.
Configure and manage the operation’s execution environment. Concurrent operations must set up their environment and report its status to clients. Specifically, the isExecuting, isFinished,
and isConcurrent methods must return appropriate values relative to the operation’s state, and these methods must be
thread-safe. These methods must also generate the appropriate key-value (KVO) observer notifications when these values change.
Note Key-value observing is an Objective-C language mechanism that enables objects to be notified of changes
to specified properties of other objects. Chapter 18 examines key-value
programming in depth.
To highlight the differences between a nonconcurrent operation object (typically executed via an operation queue) versus a concurrent operation object, let’s look at some code. Listing
17-17 illustrates the implementation of a custom, nonconcurrent operation class namedGreetingOperation.
Listing
17-17. Minimal Implementation of a Custom, Nonconcurrent Operation Class
@implementation GreetingOperation - (void)main { @autoreleasepool { @try { if (![self isCancelled]) { // Insert code to implement the task below NSLog(@"Hello, World!"); [NSThread sleepForTimeInterval:3.0]; NSLog(@"Goodbye, World!"); } } @catch (NSException *ex) {} } } @end
As shown in Listing
17-17, the code to perform the task is implemented in the main method. Note that this method includes an autorelease pool and a try-catch block. The
autorelease pool prevents memory leaks from the associated thread, while the try-catch block is required to prevent any exceptions from leaving the scope of this thread. The main method
also checks if the operation is cancelled in order to quickly terminate its execution if it is no longer needed. To invoke this operation asynchronously, you can add it to an operation queue, as shown inListing
17-18.
Listing
17-18. Executing a Custom Operation in an Operation Queue
NSOperationQueue *queue = [NSOperationQueue new]; GreetingOperation *greetingOp = [GreetingOperation new]; [greetingOp setThreadPriority:0.5]; [queue addOperation:greetingOp]; [queue waitUntilAllOperationsAreFinished];
This demonstrates the steps required to implement a nonconcurrent operation and submit it to an operation queue for execution. In the next section, you will implement a concurrent operation to understand the differences between
the two options.
Implementing Concurrent Operations
Now you will create a program that implements a custom, concurrent operation. It will provide the same functionality as the program shown in Listing17-14 and enable you to compare the differences between the two implementations. In Xcode, create a new project by selectingNew
Project
. . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify GreetingOperation for
the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check
box. Specify the location in your file system where you want the project to be created (if necessary, selectNew Folder and enter the name and location for the folder), uncheck the Source
Controlcheck box, and then click the Create button.
Next you will create the custom operation class. Select New
File
. . . from the Xcode File menu, select the Objective-C class template, and name the class GreetingOperation.
Make the class a subclass of NSOperation, select the GreetingOperation folder
for the files location and the GreetingOperation project as the target, and then click the Create button. In the Xcode project navigator pane, select the GreetingOperation.m file and update the class implementation,
as shown in Listing
17-19.
Listing
17-19. GreetingOperation Implementation
#import "GreetingOperation.h" @implementation GreetingOperation { BOOL finished; BOOL executing; } - (id)init { if ((self = [super init])) { executing = NO; finished = NO; } return self; } - (void)start { // If cancelled just return if ([self isCancelled]) { [self willChangeValueForKey:@"isFinished"]; finished = YES; [self didChangeValueForKey:@"isFinished"]; return; } // Now execute in main method a separate thread [self willChangeValueForKey:@"isExecuting"]; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; executing = YES; [self didChangeValueForKey:@"isExecuting"]; } - (void)main { @autoreleasepool { @try { if (![self isCancelled]) { NSLog(@"Hello, World!"); // Pause to simulate processing being performed by task [NSThread sleepForTimeInterval:3.0]; NSLog(@"Goodbye, World!"); [self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; executing = NO; finished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } } @catch (NSException *ex) {} } } - (BOOL)isConcurrent { return YES; } - (BOOL)isExecuting { return executing; } - (BOOL)isFinished { return finished; } @end
Compared to the nonconcurrent GreetingOperation implementation in Listing
17-17, there are a number of changes. First, observe the declaration of two private variables.
{ BOOL finished; BOOL executing; }
These variables are used to set and return the appropriate values for the isFinished andisExecuting methods.
Recall that these methods (along with the isConcurrent method) must be overridden for concurrent operations. Now
let’s look at the implementation of thestart method. This was not implemented for the nonconcurrent version of theGreetingOperation class.
First, it checks to see whether or not the operation has been cancelled; if it has, it simply sets the finished variable appropriately for KVO notifications and returns.
if ([self isCancelled]) { [self willChangeValueForKey:@"isFinished"]; finished = YES; [self didChangeValueForKey:@"isFinished"]; return; }
If not cancelled, the code sets up a new thread and uses it to invoke the main method
that implements the associated task, while also performing the appropriate KVO notifications.
[self willChangeValueForKey:@"isExecuting"]; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; executing = YES; [self didChangeValueForKey:@"isExecuting"];
Now let’s examine the class’s main method. This method has identical functionality to
that of the main method in Listing
17-14, with the addition of KVO notifications to indicate the current operation state. Also note the statement that pauses
the thread for three seconds to simulate task processing.
[NSThread sleepForTimeInterval:3.0];
Finally, the remaining methods implement the required isExecuting, isFinished,
andisConcurrent methods, returning the appropriate value in each case.
OK, now that you have finished implementing the custom operation class, let’s move on to the main() function.
In the Xcode project navigator, select the main.m file and update themain() function, as shown in Listing
17-20.
Listing
17-20. GreetingOperation main( ) Function
#import <Foundation/Foundation.h> #import "GreetingOperation.h" int main(int argc, const char * argv[]) { @autoreleasepool { GreetingOperation *greetingOp = [GreetingOperation new]; [greetingOp start]; while (![greetingOp isFinished]) ; } return 0; }
The main() function begins by creating a GreetingOperation object.
It then executes the operation by invoking its start method. Finally,
a conditional expression using the object’sisFinished method
is used to end execution of the program when the concurrent operation is finished.
When you compile and run the program, you should observe the messages in the output pane shown in Figure
17-6.
Figure
17-6. GreetingOperation program output
In the output pane, the task displays the initial greeting followed by a delay of approximately 3 seconds, and then the final message. The program exits when the thread
finishes execution per the conditional expression. As you learned from this example, a considerable amount of additional functionality must be coded to correctly implement a custom concurrent operation class. Hence,
you should only do this if you need to have an operation object execute asynchronously without adding it to a queue.
相关文章推荐
- Brief Intro to Concurrency and Threading of Foundation Framework
- Brief Intro to Archives and Serialization of Foundation Framework
- Brief Intro to Container Literals of Foundation Framework
- Brief Intro to Value Objects of Foundation Framework
- Brief Intro to File System Utilities of Foundation Framework
- Brief Intro to Object Subscripting of Foundation Framework
- Brief Intro to URL Handling of Foundation Framework (Examples: Downloading from a URL)
- Brief Intro to Notifications of Foundation Framework
- Brief Intro to Error handling of Foundation Framework
- Brief Intro to Interprocess Communication of Foundation Framework
- Brief Intro to NSError of Foundation Framework
- Brief Intro to Exception Handling of Foundation Framework
- Brief Intro to NSException of Foundation Framework
- Brief Intro to Strings of Foundation Framework
- Brief Intro to Network Services of Foundation Framework
- How To Use NSOperations and NSOperationQueues
- How To Use NSOperations and NSOperationQueues
- Brief Intro to NSNumber Literals of Foundation Frameworks
- How To Use NSOperations and NSOperationQueues
- How To Use NSOperations and NSOperationQueues