• This post was originally posted on my personal blog:”Unruly Pavilion”
  • Article links:portal

This article is used to introduce the knowledge and usage of NSOperation and NSOperationQueue in iOS multi-threading.

Through this article, you will learn:

NSOperation, introduction to NSOperationQueue, operations and operation queues, usage steps and basic usage, control serial/concurrent execution, NSOperation operation dependencies and priorities, communication between threads, thread synchronization, and thread safety, Common attributes and methods of NSOperation and NSOperationQueue.

The Demo I have put on Github, Demo link: portal

1. Introduction to NSOperation and NSOperationQueue

NSOperation and NSOperationQueue are multi-threaded solutions provided by Apple. In fact, NSOperation and NSOperationQueue are based on a higher level of ENCAPSULATION of GCD and are fully object-oriented. But it’s easier to use and more readable than GCD.

Why use NSOperation, NSOperationQueue?

  1. You can add completed blocks of code to execute after the action is complete.
  2. Add dependencies between operations to easily control the order of execution.
  3. Set the priority of the operation.
  4. You can easily cancel an operation.
  5. Use KVO to observe changes in the execution status of operations: isExecuteing, isFinished, isCancelled.

2. NSOperation, NSOperationQueue, and operation queue

Since it is a higher level of encapsulation based on GCD. Then, some of the concepts in GCD also apply to NSOperation and NSOperationQueue. There are similar concepts of tasks (operations) and queues (operation queues) in NSOperation and NSOperationQueue.

  • Operation:
    • By executing an operation, in other words, that piece of code that you execute in a thread.
    • In GCD it is placed in a block. In NSOperation, we use NSOperation subclasses NSInvocationOperation, NSBlockOperation, or custom subclasses to encapsulate operations.
  • Operation Queues:
    • The queue here refers to the operation queue, which is used to store operations. This is different from the FIFO (first in, first out) principle of scheduling queues in GCD. NSOperationQueue For operations added to the queue, the ready state is first entered (the ready state depends on the dependencies between the operations), and the start (non-end) execution order of the ready operations is determined by the relative priority of the operations (priority is an attribute of the operation object itself).
    • Operation queue by setting the maximum number of concurrent operation (maxConcurrentOperationCount) to control the concurrency, serial.
    • NSOperationQueue provides us with two different types of queues: primary queue and custom queue. The main queue runs on top of the main thread, while custom queues execute in the background.

3. Procedure for using NSOperation and NSOperationQueue

NSOperation needs to work with NSOperationQueue to implement multithreading. Because by default NSOperation is used alone and the system executes operations synchronously, we can better implement asynchronous execution with NSOperationQueue.

NSOperation implements multithreading in three steps:

  1. Create operation: Encapsulate the operation to be performed in an NSOperation object.
  2. Create a queue: Create an NSOperationQueue object.
  3. Add operations to the queue: Add the NSOperation object to the NSOperationQueue object.

After that, the system will automatically pull the NSOperation out of the NSOperationQueue and execute the operation in the new thread.

So let’s look at the basic use of NSOperation and NSOperationQueue.

4. NSOperation and NSOperationQueue are used

4.1 Creating a Vm

NSOperation is an abstract class and cannot be used to encapsulate operations. We only use a subclass of it to encapsulate the operation. We have three ways to encapsulate operations.

  1. Use subclass NSInvocationOperation
  2. Use the subclass NSBlockOperation
  3. A custom subclass derived from NSOperation that encapsulates operations by implementing corresponding methods internally.

In the case of using NSOperation alone without using NSOperationQueue, the system executes operations synchronously. Let’s learn three ways to create the following operations.

4.1.1 Using subclassesNSInvocationOperation

/** * use subclass NSInvocationOperation */
- (void)useInvocationOperation {

    // 1. Create the NSInvocationOperation object
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2. Call the start method to start the operation
    [op start];
}

/** * task 1 */
- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
        NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread}}Copy the code

Output result:

  • As you can see, in the case that an operation is performed in the main thread using subclass NSInvocationOperation alone without using NSOperationQueue, the operation is performed in the current thread and no new thread is started.

If the operation is performed in another thread, the result is printed for another thread.

// Subclass NSInvocationOperation for other threads
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
Copy the code

Output result:

  • As you can see, using subclass NSInvocationOperation alone in other threads, the operation is performed in the other thread that is currently called, without starting a new thread.

Now let’s look at NSBlockOperation.

4.1.2 Using subclassesNSBlockOperation

/** * use subclass NSBlockOperation */
- (void)useBlockOperation {

    1. Create an NSBlockOperation object
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread}}];// 2. Call the start method to start the operation
    [op start];
}
Copy the code

Output result:

  • As you can see, in the case that an operation is not performed using NSOperationQueue, using NSBlockOperation alone in the main thread, the operation is performed in the current thread and no new thread is started.

Note: Same as NSInvocationOperation used above. Because the code is called in the main thread, the result is printed in the main thread. If the operation is performed in another thread, the result is printed for another thread.

However, NSBlockOperation also provides a method, addExecutionBlock:, with which you can add additional operations to NSBlockOperation. These operations (including those in blockOperationWithBlock) can be executed simultaneously (concurrently) in different threads. Only when all related operations have been executed are considered completed.

Operations added to blockOperationWithBlock: may also be executed in other threads (other than the current thread) if multiple operations are added. This is determined by the system. It is not necessarily the case that the operations added to blockOperationWithBlock: will be executed in the current thread. Use addExecutionBlock: Try adding a few more operations.

Subclasses NSBlockOperation usage / * * * * call methods AddExecutionBlock: * / - (void) useBlockOperationAddExecutionBlock {/ / 1. NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{for (int I = 0; I < 2; I++) {[NSThread sleepForTimeInterval:2]; // simulate time operation NSLog(@"1-- %@", [NSThread currentThread]); // print currentThread}}]; // 2. Add additional operations [op addExecutionBlock:^{for (int I = 0; I < 2; I ++) {[NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "2 - % @", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "3 - % @", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "4 - % @", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "5 - % @", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "6 - % @", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ 7 -- - % @ "", [NSThread currentThread]); / / print the current thread}}]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; / / simulation time-consuming operation NSLog (@ "8 - % @", [NSThread currentThread]); / / print the current thread}}]; // 3. Call the start method to start the operation [op start]; }Copy the code

Output result:

  • See: use subclassesNSBlockOperationAnd calls the methodAddExecutionBlock:In the case of,blockOperationWithBlock:And operations in the methodaddExecutionBlock:Operations are performed asynchronously in different threads. Moreover, the results of this executionblockOperationWithBlock:The operations in the method are also not performed in the current thread (main thread). Thus confirmingblockOperationWithBlock:Operations in May also be performed in other threads (other than the current thread).

In general, if an NSBlockOperation object encapsulates multiple operations. NSBlockOperation Whether to start a new thread depends on the number of operations. If the number of operations added is large, a new thread is automatically started. Of course, the number of open threads is determined by the system.

4.1.3 Using custom subclasses derived from NSOperation

If subclasses NSInvocationOperation and NSBlockOperation are not sufficient for your daily needs, you can use custom subclasses that inherit from NSOperation. You can define your own NSOperation object by overriding the main or start methods. Overriding the main method is easy. We don’t need to manage the operation’s state properties isExecuting and isFinished. The operation ends when main completes and returns.

Define a subclass from NSOperation and override the main method.

/ / YSCOperation. H file
#import <Foundation/Foundation.h>

@interface YSCOperation : NSOperation

@end

/ / YSCOperation. M file
#import "YSCOperation.h"

@implementation YSCOperation

- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(1 - % @ "@"[NSThreadcurrentThread]); }}}@end
Copy the code

Then import the header file yscoperation.h when using it.

/** * use a custom subclass */ derived from NSOperation
- (void)useCustomOperation {
    1. Create the YSCOperation object
    YSCOperation *op = [[YSCOperation alloc] init];
    // 2. Call the start method to start the operation
    [op start];
}
Copy the code

Output result:

  • You can see that when NSOperationQueue is not used and a custom subclass inheriting from NSOperation is used separately on the main thread, the operation is performed on the main thread and no new thread is started.

So let’s talk about the creation of NSOperationQueue.

4.2 Creating a Queue

There are two types of NSOperationQueue: primary queue and custom queue. The custom queue contains both serial and concurrent functions. The following describes the basic creation methods and features of the main queue and custom queue.

  • The home side column
    • Any operations added to the main queue are executed in the main thread.
// Get the main queue
NSOperationQueue *queue = [NSOperationQueue mainQueue];
Copy the code
  • Custom queue (not primary queue)
    • Operations added to this queue are automatically placed into child threads for execution.
    • It also includes serial and concurrent functions.
// Customize the queue creation method
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
Copy the code

4.3 Adding an Operation to a Queue

We said that NSOperation needs to work with NSOperationQueue to implement multithreading.

Then we need to queue the created operations. There are two methods:

  1. - (void)addOperation:(NSOperation *)op;
    • The actions need to be created and then added to the queue.
/** * Add the operation to the operation queue */
- (void)addOperationToQueue {

    1. Create a queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. Create a vm
    Create operation 1 with NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // Create operation 2 using NSInvocationOperation
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    Create operation 3 using NSBlockOperation
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(3 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [op3 addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(4 - % @ "@"[NSThread currentThread]); // Prints the current thread}}];// 3. Run addOperation to add all operations to the queue
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}
Copy the code

Output result:

  • Use the NSOperation subclass to create the operation, and use theaddOperation:After an operation is added to the operation queue, a new thread can be opened for concurrent execution.
  1. - (void)addOperationWithBlock:(void (^)(void))block;
    • Add an operation to a block and add the block containing the operation to a queue without creating an operation.
/** * Add the operation to the operation queue */ with addOperationWithBlock

- (void)addOperationWithBlockToQueue {
    1. Create a queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. Use addOperationWithBlock to add an operation to the queue
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(2 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(3 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; }Copy the code

Output result:

  • AddOperationWithBlock: add an operation to the operation queue to start a new thread for concurrent execution.

5. NSOperationQueue controls serial and concurrent execution

As we mentioned earlier, NSOperationQueue creates a custom queue that has both serial and concurrent functions. We demonstrated concurrency above. How does this serial function work?

Here is a key attribute maxConcurrentOperationCount, called the maximum number of concurrent operation. Used to control how many operations on a particular queue can participate in concurrent execution.

Note: maxConcurrentOperationCount control is not the number of concurrent threads, but a queue at the same time the maximum number of operations that can execute concurrently. And an operation doesn’t have to run in just one thread.

  • Maximum concurrent operands:maxConcurrentOperationCount
    • maxConcurrentOperationCountBy default, the value is -1, indicating that concurrent execution is not restricted.
    • maxConcurrentOperationCountWhen 1, the queue is a serial queue. Only serial execution.
    • maxConcurrentOperationCountIf the value is greater than 1, the queue is a concurrent queue. Of course, this value should not exceed the system limit. Even if you set a large value, the system will automatically adjust to min{self-set value, the default maximum value set by the system}.
/ * * * set MaxConcurrentOperationCount (maximum number of concurrent operation) * /
- (void)setMaxConcurrentOperationCount {

    1. Create a queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. Set the maximum number of concurrent operations
    queue.maxConcurrentOperationCount = 1; // Serial queue
// queue.maxConcurrentOperationCount = 2; // Concurrent queue
// queue.maxConcurrentOperationCount = 8; // Concurrent queue

    // 3. Add operations
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(2 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(3 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(4 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; }Copy the code

The maximum concurrent operand is 1.

The maximum concurrent operand is 2.

  • As you can see, when the maximum concurrent operand is 1, the operations are executed sequentially, and the next operation is executed only after one operation is completed. When the maximum number of concurrent operations is 2, the operations are performed concurrently. Two operations can be performed simultaneously. The number of open threads is determined by the system and does not need us to manage.

In this way, is it much simpler than the COMMUNIST Party of China?

6. NSOperation Operation dependency

The most interesting thing about NSOperation and NSOperationQueue is that it can add dependencies between operations. With operation dependencies, we can easily control the order of execution between operations. NSOperation provides three interfaces for managing and viewing dependencies.

  • - (void)addDependency:(NSOperation *)op;Add a dependency that makes the current operation dependent on the completion of the operation op.
  • - (void)removeDependency:(NSOperation *)op;Remove a dependency to remove the dependency of the current operation on op.
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies;Array of all action objects that are executed before the current operation begins.

Of course, adding dependencies is often used. Now consider the requirement that, for example, there are two operations A and B, where A is done before B can perform the operation.

If you use dependencies, you need to make operation B dependent on operation A. The specific code is as follows:

/** * addDependency: */
- (void)addDependency {

    1. Create a queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. Create a vm
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread}}];NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(2 - % @ "@"[NSThread currentThread]); // Prints the current thread}}];// 3. Add dependencies
    [op2 addDependency:op1]; Op2 depends on op1, then op2 depends on op1

    // 4. Add the operation to the queue
    [queue addOperation:op1];
    [queue addOperation:op2];
}
Copy the code

Output result:

  • As you can see, by adding operation dependencies, op1 is executed first and op2 is executed later, no matter how many times the operation is run.

7. NSOperation priority

NSOperation provides the queuePriority property, which applies to operations in the same operation queue but not to operations in different operation queues. By default, all the operation of the newly created object is NSOperationQueuePriorityNormal priority. However, we can change the execution priority of the current operation in the same queue by using the setQueuePriority: method.

// Priority value
typedef NS_ENUM(NSInteger.NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = - 8 -L,
    NSOperationQueuePriorityLow = 4 -L,
    NSOperationQueuePriorityNormal = 0.NSOperationQueuePriorityHigh = 4.NSOperationQueuePriorityVeryHigh = 8
};
Copy the code

We have said above: to add to the queue in the operation, the first to enter a state of readiness (ready state depends on the dependencies between operation), and then enter the beginning of the operation of the ready state execution order (not the end of the execution sequence) is determined by the operation between relative priority (priority is the attribute of object itself operation).

So what is a ready operation?

  • When all dependencies for an operation have been completed, the operation object is usually in a ready state, waiting to execute.

For example, there are four priorities are NSOperationQueuePriorityNormal (the default level) of operation: op1, op2, op3, op4. Op3 depends on OP2, and OP2 depends on OP1, that is, OP3 -> OP2 -> OP1. Now add these four operations to the queue and execute them concurrently.

  • Since op1 and OP4 have no dependent operations, they are ready operations before op1 and OP4 are executed.
  • Op3 and OP2 both have dependent operations (op3 depends on OP2 and op2 depends on OP1), so neither op3 nor OP2 is a ready operation.

Now that we understand the ready operation, we understand the object of the queuePriority property.

  • queuePriorityThe attribute determinesEnter the operation in the ready stateStart execution order between. And priorities are no substitute for dependencies.
  • If a queue contains both high-priority and low-priority operations, and both operations are ready, the queue performs the high-priority operation first. For example, if op1 and OP4 have different priorities, the operation with higher priority will be performed first.
  • If a queue contains both ready and unready operations, the unready operations have a higher priority than the ready operations. Then, the ready operation will be executed first, even though it is of low priority. Priorities are no substitute for dependencies. If you want to control the startup sequence between operations, you must use dependencies.

8. Communication between NSOperation and NSOperationQueue threads

In iOS development, we usually do UI refreshes in the main thread, such as clicking, scrolling, dragging and so on. We usually put some time-consuming operations on other threads, such as image downloading, file uploading, etc. Communication between threads is used when we sometimes need to return to the main thread after another thread has completed a time-consuming operation.

/** * Communication between threads */
- (void)communication {

    1. Create a queue
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2. Add operations
    [queue addOperationWithBlock:^{
        // Perform time-consuming operations asynchronously
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]); // Prints the current thread
        }

        // return to the main thread
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // Perform some operations such as UI refresh
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
                NSLog(2 - % @ "@"[NSThread currentThread]); // Prints the current thread}}]; }]; }Copy the code

Output result:

  • As you can see, through communication between threads, an operation is performed in another thread, and when the operation is completed, it is returned to the main thread to perform the corresponding operation in the main thread.

9. NSOperation, NSOperationQueue Thread synchronization and thread safety

  • Thread safety: If your code is in a process that has multiple threads running at the same time, those threads may be running the code at the same time. If the result of each run is the same as the result of a single thread run, and the values of other variables are the same as expected, it is thread-safe. If there are only reads and no writes on a global variable or static variable per thread, the global variable is generally thread-safe. If you have multiple threads doing writes (changing variables) at the same time, you generally need to consider thread synchronization, otherwise it may affect thread safety.
  • Thread synchronization: it can be understood as thread A and thread B cooperate together. When thread A performs to A certain extent, it depends on A result of thread B, so it stops and signals B to run. B does what he says and gives the result to A; A Continue operations.

Here’s a simple example: Two people talking together. Two people cannot speak at the same time to avoid inaudible (operational conflict). Wait for one person to finish (one thread finishes the operation), then the other one speaks (the other thread starts the operation).

Next, we simulate train ticket sales to implement NSOperation thread security and solve thread synchronization problems. Scene: There are 50 train tickets in total, and there are two Windows for selling train tickets, one is for Beijing train tickets, the other is for Shanghai train tickets. Tickets will be sold at both Windows until they are sold out.

9.1 NSOperation and NSOperationQueue are not thread safe

Let’s start with code that doesn’t consider thread safety:

/** * non-thread-safe: do not use NSLock * to initialize the number of train tickets, ticket window (non-thread-safe), and start selling tickets */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@"[NSThread currentThread]); // Prints the current thread

    self.ticketSurplusCount = 50;

    // 1. Create queue1, which stands for Beijing train ticket sales window
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;

    // 2. Create queue2, which represents the Shanghai train ticket sales window
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;

    // 3. Create operation op1
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketNotSafe];
    }];

    // 4. Create op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketNotSafe];
    }];

    // 5. Add operation to start selling tickets
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

/** * Sell train tickets (not thread safe) */
- (void)saleTicketNotSafe {
    while (1) {

        if (self.ticketSurplusCount > 0) {
            // If tickets are available, go on sale
            self.ticketSurplusCount--;
            NSLog(@ "% @"[NSString stringWithFormat:"Remaining votes :%d window :%@".self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@" All train tickets sold out");
            break; }}}Copy the code

Output result:

.

  • As you can see, getting votes without considering thread safety and using NSLock is insane, which obviously doesn’t meet our needs, so we need to consider thread safety.

9.2 NSOperation and NSOperationQueue are not thread safe

Thread-safe solution: Locks can be placed on threads so that while one thread is performing the operation, other threads are not allowed to perform the operation. IOS implements thread locking in a number of ways. @ synchronized, NSLock, NSRecursiveLock, NSCondition, NSConditionLock, pthread_mutex, OSSpinLock, atomic(property) Set/GE and so on. Here we use the NSLock object to solve the thread synchronization problem. NSLock objects can be thread-safe by calling lock when a lock is entered and unlock when it is unlocked.

Consider thread-safe code:

/** * thread-safe: use NSLock to initialize the number of train tickets, the window for selling tickets (thread-safe), and start selling tickets */

- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@"[NSThread currentThread]); // Prints the current thread

    self.ticketSurplusCount = 50;

    self.lock = [[NSLock alloc] init];  // Initializes the NSLock object

    // 1. Create queue1, which stands for Beijing train ticket sales window
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;

    // 2. Create queue2, which represents the Shanghai train ticket sales window
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;

    // 3. Create operation op1
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];

    // 4. Create op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];

    // 5. Add operation to start selling tickets
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

/** * sell train tickets (thread safety) */
- (void)saleTicketSafe {
    while (1) {

        / / lock
        [self.lock lock];

        if (self.ticketSurplusCount > 0) {
            // If tickets are available, go on sale
            self.ticketSurplusCount--;
            NSLog(@ "% @"[NSString stringWithFormat:"Remaining votes :%d window :%@".self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }

        / / unlock
        [self.lock unlock];

        if (self.ticketSurplusCount <= 0) {
            NSLog(@" All train tickets sold out");
            break; }}}Copy the code

Output result:

.

  • It can be seen that: considering thread safety and using NSLock locking and unlocking mechanism, the number of votes obtained is correct and there is no confusion. We have solved the problem of multi-thread synchronization.

10. Summary of common attributes and methods of NSOperation and NSOperationQueue

10.1 Common attributes and methods of NSOperation

  1. Cancel operation method
    • - (void)cancel;Operations can be cancelled, essentially marking isCancelled status.
  2. Method to determine operation status
    • - (BOOL)isFinished;Check whether the operation is complete.
    • - (BOOL)isCancelled;Determines whether the operation has been marked as cancelled.
    • - (BOOL)isExecuting;Determine whether the operation is running.
    • - (BOOL)isReady;Determines whether an operation is in a ready state. This value depends on the dependency of the operation.
  3. Synchronous operation
    • - (void)waitUntilFinished;Blocks the current thread until the operation is complete. Can be used for sequential synchronization of threads.
    • - (void)setCompletionBlock:(void (^)(void))block; completionBlockExecutes the completionBlock when the current operation completes.
    • - (void)addDependency:(NSOperation *)op;Add a dependency that makes the current operation dependent on the completion of the operation op.
    • - (void)removeDependency:(NSOperation *)op;Remove a dependency to remove the dependency of the current operation on op.
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies;Array of all action objects that are executed before the current operation begins.

10.2 Common Attributes and Methods of NSOperationQueue

  1. Cancel/pause/resume action
    • - (void)cancelAllOperations;You can cancel all operations on the queue.
    • - (BOOL)isSuspended;Determines whether the queue is paused. YES indicates the pause state, and NO indicates the recovery state.
    • - (void)setSuspended:(BOOL)b;You can set the pause and resume of an operation. YES stands for pause queue, and NO stands for resume queue.
  2. Synchronous operation
    • - (void)waitUntilAllOperationsAreFinished;Blocks the current thread until all operations in the queue have completed.
  3. Add/get operations
    • - (void)addOperationWithBlock:(void (^)(void))block;Add an operation object of type NSBlockOperation to the queue.
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;Add an array of operations to the queue and wait to block the current thread until all operations are complete
    • - (NSArray *)operations;An array of operations currently in the queue that are automatically cleared when an operation completes.
    • - (NSUInteger)operationCount;Operands in the current queue.
  4. Access to the queue
    • + (id)currentQueue;Gets the current queue, and returns nil if the current thread is not running on NSOperationQueue.
    • + (id)mainQueue;Gets the main queue.

Note:

  1. Pause and cancel (both the cancellation of operations and the cancellation of queues) do not mean that the current operation can be cancelled immediately, but that no new operation can be performed after the current operation has been completed.
  2. The difference between a pause and a cancel is that after a pause, you can resume the operation and continue. After canceling, all operations are cleared and no further operations can be performed.

References:

  • Apple Official – Concurrency Programming Guide: Operation Queues (Recommended)
  • Apple official document: NSOperation
  • Objc China: Concurrent Programming: apis and Challenges

IOS multithreading exhaustive summary series of articles:

  • IOS multithreading: a detailed summary of “pthread, NSThread”
  • IOS multithreading: an exhaustive summary of “GCD”
  • IOS multithreading: summary of NSOperation, NSOperationQueue
  • IOS multithreading: a thorough summary of “RunLoop”