1. Let’s start with a few concepts

NSOperation: Literal meaning: operation.

An operation is the process of “performing one thing”, which can have one task or multiple tasks. For example, in NSBlockOperation, you can append tasks to be operated by addExecutionBlock. In iOS, NSOperation is an abstract class, and we need to use specific subclasses of NSOperation, including NSInvocationOperation, NSBlockOperation, You can also customize a subclass of NSOperation.

NSOperationQueue: Indicates an operation queue.

In iOS, there are two types of operation queues: one is the main operation queue provided by the system. The main operation queue runs in the main thread and is obtained by [NSOperationQueue mainQueue] without developer creation. The other is the operation queue created by ourselves.

An action queue is like a container for operations. We create the container, and then we put the created operations into the container, and then the system starts to execute the operations.

2. How to implement multithreaded programming with NSOperation/NSOperationQueue

NSOperation/NSOperationQueue implement multithreaded programming is divided into three steps in total.

The first step is to create a container, which is to create an operation queue.

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

Step 2, create the action to perform.

Third, add the created operation to the action queue.

3. The use of NSOperation

When you use the operation alone, you need to call star on the operation object to start the operation.

3.1 Use NSInvocationOperation on the main thread

- (void)invocationOperationOnMainThread { NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil]; [invacationOperation start]; }... - (void)doWork { for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"A ----- %d -----%@",i,[NSThread currentThread]); }}Copy the code

Console output:

Note The operation is performed on the current main thread, and no new thread is started to perform the operation.

3.2 Use NSInvocationOperation in threading

[NSThread detachNewThreadSelector:@selector(invocationOperationOnOtherThread) toTarget:self withObject:nil ]; ... - (void) invocationOperationOnOtherThread {NSLog (@ "- the begin -- -- --"); NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil]; [invacationOperation start]; NSLog(@"--- end ---"); }Copy the code

Console output:

Note When subclass NSInvocationOperation is used in another thread, the operation is performed in the other thread, and the new thread is not started.

3.3 Using NSBlockOperation on the main thread

When performing an NSBlockOperation on the main thread:

- (void)blockOperationOnMainThread { NSLog(@"--- begin ---"); NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread  sleepForTimeInterval:2]; NSLog(@"A ----- %d ----- %@",i,[NSThread currentThread]); } }]; [blockOperation start]; NSLog(@"--- end ---"); }Copy the code

Console output:

Note If an operation is performed using NSBlockOperation alone in the main thread, the operation is performed on the current main thread without starting a new thread.

What if I append a few operations?

# pragma mark - the main thread to use NSBlockOperation - (void) blockOperationOnMainThread {NSLog (@ "- the begin -- -- --"); NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread  sleepForTimeInterval:2]; NSLog(@"A ----- %d ----- %@",i,[NSThread currentThread]); } }]; // If the following ExecutionBlock tasks are not added, then task A will be executed in the main thread. If multiple tasks are added, then some of the subsequent tasks will be executed in the split thread. [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"B -----%d  ----- %@",i,[NSThread currentThread]); } }]; [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]); } }]; [blockOperation start]; NSLog(@"--- end ---"); }Copy the code

Console output:

Note If multiple NSBlockOperations are added, the system will automatically open multiple threads to perform the operation. Whether to perform the operation in the main thread or in the sub-thread of the block depends on the system.

3.4 Using NSBlockOperation in Threading

[NSThread detachNewThreadSelector:@selector(blockOperationOnOtherThread) toTarget:self withObject:nil]; ... # pragma mark - threads to use NSBlockOperation - (void) blockOperationOnOtherThread {NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"A  ----- %d ----- %@",i,[NSThread currentThread]); } }]; [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"B -----%d  ----- %@",i,[NSThread currentThread]); } }]; [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]); } }]; [blockOperation start]; }Copy the code

Console output:

Note If multiple NSBlockOperations are executed in separate threads, the system automatically starts multiple threads for asynchronous operations based on multiple operation tasks.

3.5 Operation and Operation queue configuration and use to implement multi-threaded asynchronous execution

- (void) creatOperationQueueAddOperation {/ / create operation queue OperationQueue NSOperationQueue * OperationQueue = [[NSOperationQueue alloc] init]; // Create Operation if only doWork task is executed in split thread. NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil]; NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread  sleepForTimeInterval:2]; NSLog(@"B ----- %d -----%@",i,[NSThread currentThread]); } }]; [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i ++) { [NSThread sleepForTimeInterval:2]; NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]); } }]; [operationQueue addOperationWithBlock:^{for (int I = 0; I < 2; I ++) {[NSThread sleepForTimeInterval:2]; NSLog(@"D ----- %d -----%@",i,[NSThread currentThread]); } }]; [operationQueue addOperation:invocationOperation]; / / addOperation operation operation will be automatic star [operationQueue addOperation: blockOperation]; }Copy the code

Console output:

After begin and end, the operation is created using the NSOperation subclass, and addOperation: adds the operation to the operation queue to enable concurrent execution of the operation.

The addOperation operation automatically stars operation

You can use addOperationWithBlock to add an action task directly to the action queue. AddOperationWithBlock adds an NSBlockOperation directly.

3.6 Inter-operation scheduling/communication

Usage scenario: Start an operation queue to perform complex and time-consuming operations, and then schedule to the main queue to update the UI

#pragma mark - (void)operationCommunicate {NSOperationQueue *queue =[[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"C ---%@", [NSThread currentThread]);} // Schedule execution to the main thread such as UI refresh [[NSOperationQueue mainQueue] addOperationWithBlock:^{for (int I = 0; I < 2; I ++) {NSThread sleepForTimeInterval:2]; NSLog(@" main thread execution %d --%@", I,[NSThread currentThread]);}}];}]; }Copy the code

Console output:

3.7 set the maximum number of concurrent control serial/concurrent setMaxConcurrentOperationCount

# pragma mark - use maxConcurrentOperationCount (maximum concurrency) serial/parallel - parameter Settings (void)exchangeSerialOrConcurrentQueueWithMaxConcurrentCount { NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // queue.maxConcurrentOperationCount = 0; / / do not perform / / queue. MaxConcurrentOperationCount = 1; / / the default value is 1, directly open the concurrent queue. / / the queue maxConcurrentOperationCount = 1; / / / / serial queue queue. MaxConcurrentOperationCount = 2; Two concurrent / / / / queue. MaxConcurrentOperationCount = 4; [queue addOperationWithBlock:^{for (int I = 0; I < 2; I ++) {[NSThread sleepForTimeInterval:2]; NSLog(@"A-- %@",  [NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"B---%@", [NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"C---%@", [NSThread currentThread]); } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"D---%@", [NSThread currentThread]); } }]; }Copy the code

When the queue. The maxConcurrentOperationCount = 1, the console output is:

When the queue. MaxConcurrentOperationCount = 2, the console output is:

Can set different maxConcurrentOperationCount, can realize serial and concurrent control task execution.

Also ** Sets the maximum number of concurrent operations not equal to the number of open threads. How many threads are opened to execute is controlled by the system. Arunachal Pradesh

3.8 Operation Dependency addDependency

AddDependency - (void) addOperationDependency {NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil]; NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"B---%@", [NSThread currentThread]); } }]; [blockOperation addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; NSLog(@"B addExecution ---%@", [NSThread currentThread]); } }]; NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread  sleepForTimeInterval:2]; NSLog(@"C ---%@", [NSThread currentThread]); } }]; / / / / dependence invocationOperation - "blockOperation -" blockOperation2 [invocationOperation addDependency: blockOperation]; // invocationOperation depends on blockOperation. In the execution invocationOperation [blockOperation addDependency: blockOperation2]; [queue addOperation:invocationOperation]; [queue addOperation:blockOperation]; [queue addOperation:blockOperation2]; // [queue addOperations:@[invocationOperation,blockOperation,blockOperation2] waitUntilFinished:NO]; }Copy the code

Console output:

Add dependencies dependencies invocationOperation — blockOperation — After blockOperation2, Execution sequence: blockOperation2 — blockOperation — invocationOperation.

3.9 Operation priority setQueuePriority

In addOperationDependency approach, we place priority on invocationOperation NSOperationQueuePriorityHigh, when executed after order no change, shows there is dependent relationship between the operation of the task, The order of execution does not change because of a priority change, and priority setting is not a substitute for breaking/dependency relationships.

4. Thread safety/data safety

Lock NSOperationQueue to ensure thread safety and data security. The locking method is similar to GCD. You can use semaphore to add a spin lock, synchronized to add a spin lock, and NSLock can be used.

- (void)initTicketStatusSave {NSLog(@"currentThread-- %@",[NSThread currentThread]); // Prints the current thread NSLog(@"semaphore-- --begin"); __weak __typeof(self)weakSelf = self; self.ticketSurplusCount = 5; NSOperationQueue *queue1 = [[NSOperationQueue alloc] init]; // Queue1 = [NSOperationQueue alloc] init]; queue1.maxConcurrentOperationCount = 1; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // Queue2 = [NSOperationQueue alloc] init]; queue2.maxConcurrentOperationCount = 1; NSBlockOperation *shanghaiOperation = [NSBlockOperation blockOperationWithBlock:^{[weakSelf saleTicketSafe]; NSBlockOperation *shanghaiOperation = [NSBlockOperation blockOperationWithBlock:^{[weakSelf saleTicketSafe]; }]; NSBlockOperation *beijingOperation = [NSBlockOperation blockOperationWithBlock:^{ [weakSelf saleTicketSafe]; }]; / / will be added to the operation queue [queue1 addOperation: shanghaiOperation]; [queue2 addOperation:beijingOperation]; } - (void)saleTicketSafe {// while (1) {// // = // dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); If (self.ticketSurplusCount > 0) {// if (self.ticketSurplusCount > 0) {// If (self.ticketSurplusCount > 0) {// If (self.ticketSurplusCount > 0) // NSLog(@"%@", [NSString stringWithFormat:@" residual votes: %ld window: %@", _ticketSurplusCount, [NSThread currentThread]]); / / [NSThread sleepForTimeInterval: 0.2]; } else {// if all tickets are sold out, close the ticket window // NSLog(@" all tickets are sold out "); // dispatch_semaphore_signal(_semaphore); // break; // } // dispatch_semaphore_signal(_semaphore); / / / /} NSLock / / while (1) {lock / / / / / / / self. The lock lock; // if (self.ticketSurplusCount > 0) {// // if there are still tickets, continue to sell // self.ticketsurpluscount --; // NSLog(@"%@", [NSString stringWithFormat:@" residual votes :%d window :%@", self.ticketSurplusCount, [NSThread currentThread]]); / / [NSThread sleepForTimeInterval: 0.2]; //} // // unlock // [self.lock unlock]; // // if (self.ticketsurpluscount <= 0) {// NSLog(@" all tickets are sold out "); // break; @synchronized(self) {if (self.ticketsurpluscount > 0) {if (self.ticketsurpluscount > 0) { Continue selling self.ticketSurplusCount--; NSLog(@"%@", [NSString stringWithFormat:@" Remaining votes: %ld window: %@", _ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval: 0.2]; } else {// If all tickets are sold out, close the ticket window NSLog(@" all tickets are sold out "); break; }}}}Copy the code

Console output:

5. NSOperation/NSOperationQueue and the difference between the GCD

  1. GCD is the API made up of the underlying C language, while NSOperationQueue is encapsulated based on GCD, and its related objects are objects of Objc. In GCD, tasks are performed in queues consisting of blocks, which are lightweight data structures; Operation, as an object, gives us more options;

  2. NSOperation makes it easy to set up dependencies. We can make one Operation depend on another, so that even though both operations are in the same parallel queue, the former will not execute until the latter is finished.

  3. NSOperation exposes a number of properties and apis.

We can use – (void)cancel; Cancel the operation;

– (BOOL)isFinished Indicates whether the operation is complete.

– (BOOL)isCancelled Determines whether the operation has been marked as cancelled;

– (BOOL)isExecuting Determines whether an operation is running.

– (BOOL)isReady indicates whether the operation isReady.

Therefore, by means of KVO, we can easily know the execution state of NSOperation, that is, the intermediate state. We can also cancel tasks that have been set to be executed at any time, and the GCD cannot stop tasks that have been added to the queue block.

Note: When the cancel method is called, the already started operation cannot be blocked, only the next operation in the queue that is ready to be executed.

  1. In NSOperation, we can set the priority of NSOperation so that tasks in the same parallel queue can be executed sequentially.

  2. GCD is preferred if the tasks are not too dependent on each other, and NSOperationQueue is preferred if there are dependencies between the tasks or if we want to listen to their execution.