With the upgrading of mobile devices, the performance of mobile devices is also increasing, now the popular CPU has entered the dual-core, or even four-core era. How to maximize the performance of these CPUs will become more and more important. In iOS, if you want to take advantage of the multi-core CPU, you need to use concurrent programming to improve CPU utilization. There are 2 main ways to Operation queue and GCD (Grand Central Dispatch) in concurrent programming in iOS. Let's take a look at the operation Queue.
Asynchronous calls and concurrency
Before you go deep, talk about asynchronous calls and concurrency first. These two concepts are easily confused in concurrent programming. An asynchronous call is a call that is called without waiting for the result to be returned, and an asynchronous call often triggers a background threading, such as a nsurlconnection asynchronous network callback. Concurrency refers to the simultaneous execution of multiple tasks (threads). Concurrency mechanisms are often used in implementations of asynchronous invocations, but not all of them are concurrency mechanisms, or other mechanisms, such as those that rely on interrupts.
Why Operation Queue
The Operation queue provides an object-oriented concurrency programming interface that supports multiple configurations such as concurrency, thread prioritization, task prioritization, and task dependencies, and can be easily adapted to a variety of complex multitasking scenarios.
- Object-oriented interface
- Supports concurrent number configuration
- Task priority scheduling
- task dependencies
- Thread-Priority configuration
Nsoperation Introduction
In iOS concurrent programming, each concurrency task is defined as a operation, and the corresponding class name is nsoperation. Nsoperation is an abstract class that cannot be used directly, it only defines some basic methods of operation. We need to create a subclass that inherits from it or uses a system-predefined subclass. Currently, the system has two sub-classes defined: Nsinvocationoperation and nsblockoperation.
Nsinvocationoperation
Nsinvoationoperation is an object-based and selector operation, using this you only need to specify the object and the selector of the task, and if necessary, you can also set the object parameters passed.
NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithObj:) object:obj];
At the same time, when this operation is complete, you can also get the result object returned after invation execution in operation.
id result = [invacationOperation result];
Nsblockoperation
To perform a task in a block, we need to use nsblockoperation. You can blockOperationWithBlock: easily create a nsblockoperation by using a method:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ //Do something here.}];
Run a operation
Call Operation's start method to run a operation directly.
[operation start];
startMethod is used to start a operation task. At the same time, operation provides a main way for all your tasks to be handled in main. The default start method will first make some exception judgments and then call the main method directly. If you need to customize a nsoperation you must overload main the method to perform the task that you want to perform.
@implementation CustomOperation-(void)main { @try { // Do some work. } @catch(...) { // Exception handle. }}@end
Cancel a operation
To cancel a operation, send the cancel message to the Operation object:
[operation cancel];
When you send a cancel message to a Operation object, there is no guarantee that the Operation object will be canceled immediately, depending on the main cancel handling of the pair. If you main don't do any processing in the method cancel , sending a cancel message has no effect. In order for the operation to respond to the cancel message, you main should manually determine the property in some appropriate place in the method, isCancelled and if you return YES , you should release the resource and immediately stop execution.
Create a operation that can be concurrent
Because the main method is called directly by default in the Operation start method, there is a more time-consuming processing task in the main method. If we have more than one operation in a row of code start , these operation are blocked sequentially, because the second operation must wait until the first operation executes start inside the main and return. Operation default is non-concurrent (except with the operation queue case, Operation queue manages its own thread), because operation does not create additional threads by default. We can use the operation isconcurrent method to determine whether operation is concurrency-capable. If you want operation to be concurrent, we need to have main execute in a separate thread and return isconcurrent to Yes.
@implementation MyOperation{ BOOL executing; BOOL finished;}- (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 { // Do some work. [self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; executing = NO; finished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } @catch(...) { // Exception handle. }}@end
When you customize start or main method, be sure to manually call some KVO notification methods, so that the object's KVO mechanism can function properly.
Set the Completionblock of operation
Each operation can be set to completionBlock execute the block automatically when the operation execution completes. We can do some of the processing here. The completionBlock principle of implementation is to kvo the field of the Operation isFinnshed (Key-value observing) and execute it when the supervisor hears isFinnished it as Yes completionBlock .
operation.completionBlock = ^{ NSLog(@"finished");};
Set thread priority for operation
We can set a thread priority for operation, that is threadPriority . At main the time of execution, the thread priority is adjusted to the thread priority set. The default value is 0.5, and we can modify it before operation executes.
operation.threadPriority = 0.1;
Note: If you reload the start method, then you need to configure main the thread precedence and the fields to be threadPriority consistent when executing.
Operation State Change
We can use the KVO mechanism to monitor operation state changes, such as a operation execution state or completion state. The keypath of these states include the following:
- IsCancelled
- Isconcurrent
- Isexecuting
- Isfinished
- IsReady
- Dependencies
- Queuepriority
- Completionblock
Nsoperationqueue
Nsoperationqueue is a operation execution queue, and you can add any operation that you want to perform to the operation queue to execute in queues. The operation and operation queues also offer a number of configurable options. In the implementation of the Operation queue, one or more manageable threads are created, providing a highly customizable execution environment for the operation in the queue.
Operation's dependency relationship
Sometimes we have requirements for the order in which the tasks are executed, and one task must be completed before another task executes, which requires the use of the operation dependency (Dependency) attribute. We can set some other operation for each operation, so if the dependent operation is not fully executed, the operation will not be executed.
[operation addDependency:anotherOperation];[operation removeDependency:anotherOperation];
If these operation and the operation that it relies on are added to the queue, operation can only be executed if the operation it relies on is executed. This allows us to conveniently control the order of operation execution.
Operation the priority that is performed in the queue
Operation is performed by default in the queue in the FIFO (first in first out) order. At the same time we can set an execution priority for a single operation, disrupting this order. When a queue has idle resources to execute a new operation, the pending operation that have the highest priority in the current queue is executed first.
Maximum concurrent operation number
In a operation queue where multiple operation can be executed at the same time, operation queue dynamically creates multiple threads to complete the corresponding operation. The specific number of threads is optimized for configuration by the operation queue, which generally depends on the performance of the system CPU, such as the CPU core number, and the CPU load. However, if we can set a maximum concurrency, the operation queue will not create threads that exceed the maximum concurrent number.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount = 1;
If we are maxConcurrentOperationCount set to 1 , then only one task can be performed at a time in the queue. This is a serial execution queue.
Simple Code
Below I have written a simple code to illustrate the operation and Operation Queue.
NSBlockOperation *operation5s = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation5s begin"); sleep(5); NSLog(@"operation5s end");}];operation5s.queuePriority = NSOperationQueuePriorityHigh;NSBlockOperation *operation1s = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation1s begin"); sleep(1); NSLog(@"operation1s end");}];NSBlockOperation *operation2s = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation2s begin"); sleep(2); NSLog(@"operation2s end");}];operation1s.completionBlock = ^{ NSLog(@"operation1s finished in completionBlock");};NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount = 1;[queue addOperation:operation1s];[queue addOperation:operation2s];[queue addOperation:operation5s];[queue waitUntilAllOperationsAreFinished];
Running this code, I get a bit of the output:
operation1s beginoperation1s endoperation5s beginoperation1s finished in completionBlockoperation5s endoperation2s beginoperation2s end
In order to better show the queue priority effect, I have maxConcurrentOperationCount set up queue 1 so that the task is executed one by one. As can be seen from the above log, after the first operation1s executes, operation5s is executed, not operation2s, because operation5s queuePriority is NSOperationQueuePriorityHigh . The first thread is always the first one to execute. Looking at line 2-4, we can see that operation1s completionBlock started execution later than Operation5s, stating that it was not executed in the operation1s thread. As previously mentioned, completionBlock it is performed through KVO monitoring, which is typically run on the thread that listens to, not operation.
Precautions
- When a operation is added to the queue, please do not make any further changes to this operation. Because once the queue is added, it can be executed at any time, and any modification to it may result in an uncontrollable state of its operation.
threadPriorityOnly affects the main thread priority at execution time, and other methods include the completionBlock default priority. If you customize it, be careful to main set it up before execution threadPriority , and restore the default thread priority after execution is complete.
- As tested, Operation
threadPriority fields are valid only when operation is executed alone, and are not valid in the Operation queue.
- The first operation added to the operation queue, no matter how low its priority, will always be the first to execute.
Concurrent programming of the Operation Queue