NSOperation and NSOperationQueue are a set of multi-threaded solutions provided by Apple. Is based on a higher level of encapsulation GCD, fully object-oriented. But it is easier to use than GCD and the code is more readable.
Why use NSOperation, NSOperationQueue?
- You can add blocks of code that will be executed after the operation is complete.
- Add dependencies between operations for easy control of execution sequence.
- Sets the priority at which the operation is executed.
- It is easy to cancel the execution of an operation.
- Use KVO to observe changes to the execution status of an operation: isExecuteing, isFinished, isCancelled.
- The meaning of performing an operation, in other words, is the piece of code that you execute in the thread.
- In GCD they are placed in blocks. In NSOperation, we use NSOperation subclasses NSInvocationOperation, NSBlockOperation, or custom subclasses to encapsulate the operation.
- The queue here refers to the operation queue, that is, the queue used to store the operation. This is different from the SCHEDULING queue FIFO (first in, first out) principle 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 then the start order (not the end order) of the operations entering the ready state is determined by the relative priority of the operations (the 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: a main queue and a custom queue. The main queue runs on top of the main thread, while the custom queue executes in the background
Using the step
Because by default, when NSOperation is used alone, the system performs the operation synchronously, and with NSOperationQueue we can do a better job of asynchronous execution.
NSOperation realizes the use of multithreading in three steps:
- Create operation: Encapsulate the operation to be performed into an NSOperation object.
- Create queue: Create an NSOperationQueue object.
- Queue an operation: Adds an NSOperation object to an NSOperationQueue object.
After that, the system will automatically pull the NSOperation out of the NSOperationQueue and perform the operation in the new thread.
The basic use
NSOperation is an abstract class and cannot be used to encapsulate operations. We have to use subclasses of it to encapsulate operations. We have three ways to encapsulate operations.
- Use the subclass NSInvocationOperation
- Use the subclass NSBlockOperation
- Custom subclasses of NSOperation, implementing internal methods to encapsulate operations.
In the case of using NSOperation alone without NSOperationQueue, the system synchronizes the operation. We will learn three ways to create the following operation.
1. Use the NSInvocationOperation subclass
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; // 2. Call the start method to start the operation [op start]; }Copy the code
In the case where an operation is performed on the main thread using the subclass NSInvocationOperation without using NSOperationQueue, the operation is performed on the current thread and no new thread is opened.
If the operation is performed in another thread, the result is printed for another thread.
2. Use the NSBlockOperation subclass
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"1 - % @", [NSThread currentThread]); // Print the current thread}}]; // 2. Call the start method to start the operation [op start];Copy the code
- As you can see, when an operation is performed using NSBlockOperation alone in the main thread without using NSOperationQueue, the operation is performed on the current thread and no new thread is opened.
Note: Same as NSInvocationOperation used above. Because the code is invoked on the main thread, the result is printed on 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 performed simultaneously (concurrently) in different threads. It is considered complete only when all related operations have been completed.
If more operations are added, the operations in blockOperationWithBlock: may also be executed in other threads (other than the current thread). This is determined by the system. This does not mean that the operations added to blockOperationWithBlock: will necessarily be executed in the current thread. (You can use addExecutionBlock: add a few more operations.)
In general, if an NSBlockOperation object encapsulates multiple operations. NSBlockOperation Whether to enable new threads depends on the number of operations. If the number of operations added is large, the new thread is automatically started. Of course, the number of threads opened is determined by the system.
3. Use a custom subclass that inherits from NSOperation
You can define your own NSOperation object by overriding main or start methods. Overwriting the main method is easy. We don’t need to manage the isExecuting and isFinished state properties of the operation. The operation ends when main returns.
- We can see that in the case where NSOperationQueue is not used and the main thread uses a custom subclass inherited from NSOperation alone, the operation is performed on the main thread and no new thread is opened.
Create a queue
There are two types of NSOperationQueue: main queue and custom queue.
- The queue
- Any operations added to the main queue are executed in the main thread.
NSOperationQueue *queue = [NSOperationQueue mainQueue];Copy the code
- Any operations added to the main queue are executed in the main thread.
- Custom queue (not main queue)
- Operations added to such a queue are automatically placed in the child thread for execution.
- It also includes: serial and concurrent functions.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];Copy the code
Add the action to the queue
There are two methods:
- (void)addOperation:(NSOperation *)op;
- You need to create the operation first and then add the created operation to the created queue
/** * Use addOperation: queue the operation */ - (void)addOperationToQueue {// 1. Creating a queue NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"3 - % @", [NSThread currentThread]); // Print the current thread}}]; [op3 addExecutionBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"4 - % @", [NSThread currentThread]); // Print the current thread}}]; // 3. Run addOperation: add all operations to the queue [queue addOperation:op1]; // [op1 start] [queue addOperation:op2]; // [op2 start] [queue addOperation:op3]; // [op3 start] }Copy the code
- You need to create the operation first and then add the created operation to the created queue
- As you can see: Use the NSOperation subclass to create the operation and use the
addOperation:
Adding an operation to the operation queue can start a new thread for concurrent execution.
2. - (void)addOperationWithBlock:(void (^)(void))block;
Add the operation to the block without creating the operation first, and the block containing the operation is directly added to the queue.
/ * * * using addOperationWithBlock: will be added to the operation queue * / - (void) addOperationWithBlockToQueue {/ / 1. Creating a queue NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2. Add an operation to a queue using addOperationWithBlock [queue addOperationWithBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"1 - % @", [NSThread currentThread]); // Print the current thread}}]; [queue addOperationWithBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"2 - % @", [NSThread currentThread]); // Print the current thread}}]; [queue addOperationWithBlock:^{for(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"3 - % @", [NSThread currentThread]); // Print the current thread}}]; }Copy the code
- AddOperationWithBlock: Adding an operation to the operation queue can open a new thread for concurrent execution.
NSOperationQueue controls serial and concurrent execution
1, the key attributes: maxConcurrentOperationCount, called the maximum number of concurrent operation. Used to control how many operations can participate in concurrent execution in a particular queue.
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 does not have to run in only one thread.
Maximum number of concurrent operation: maxConcurrentOperationCount
-
The default value is -1, indicating that concurrent execution is not restricted
Is 1, the queue is a serial queue. It can only be executed sequentially.
If the value is greater than 1, the queue is a concurrent queue. Of course, the value should not exceed the system limit. Even if you set a large value, the system will automatically adjust to min{the value set by yourself, the default maximum value set by the system}.
- As you can see, when the maximum concurrent operand is 1, the operations are executed sequentially, and one operation is completed before the next operation is executed. When the maximum number of concurrent operations is 2, the operations are performed concurrently and two operations can be performed at the same time. The number of open threads is determined by the system and not managed by us.
Is it easier than the COMMUNIST Party?
NSOperation Operation dependency
The most attractive aspect is the ability to add dependencies between operations. With operational dependencies, we can easily control the sequence of execution between operations. NSOperation provides three interfaces for us to manage and view dependencies.
- (void)addDependency:(NSOperation *)op;
Adds a dependency that makes the current operation dependent on the completion of the operation OP.- (void)removeDependency:(NSOperation *)op;
Remove the dependence of the current operation on the op.@property (readonly, copy) NSArray<NSOperation *> *dependencies;
An array of all operation objects that completed execution before the current operation began.
For example, there are two operations: A and B. B can perform the operation only after A has performed the operation.
If dependency processing is used, then operation B needs to depend on operation A.
NSOperation priority
QueuePriority property. The queuePriority property applies to the operations in the same operation queue but not to the operations in different operation queues. By default, all the operation of the newly created object is NSOperationQueuePriorityNormal priority. However, we can change the priority of the current operation in the same queue by using the setQueuePriority: method.
/ / priority values typedef NS_ENUM (NSInteger NSOperationQueuePriority) {NSOperationQueuePriorityVeryLow = 8 l, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8 };Copy the code
For operations added to the queue, they first enter the ready state (the ready state depends on the dependencies between the operations), and then the start order (not the end order) of the operations that enter the ready state is determined by their relative priority (which is a property of the operation object itself).
A list of operations with a default Normal priority. None of the operations that need to be dependent on will be in the ready state first. The priority attribute determines the start order of execution between operations that enter the ready state. Also, priorities do not replace dependencies.
Dependencies > Priorities Priorities do not replace dependencies. If you want to control the start order between operations, you must use dependencies
Communication between NSOperation and NSOperationQueue threads
Generally in the main thread inside the UI refresh, such as: click, scroll, drag and other events. We usually put time-consuming operations in other threads
Interthread communication 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. Creating a queue NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 2. Add operation [queue addOperationWithBlock:^{// Asynchronously perform time-consuming operationfor(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"1 - % @", [NSThread currentThread]); } // Return to the main thread [[NSOperationQueue mainQueue] addOperationWithBlock:^{// do some UI refresh and so onfor(int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // Simulate the time-consuming NSLog(@) operation"2 - % @", [NSThread currentThread]); // Print the current thread}}]; }]; }Copy the code
NSOperation and NSOperationQueue thread synchronization and thread safety
- Thread-safe: If your code is in a process where multiple threads are running at the same time, these threads may run the code at the same time. It is thread-safe if the result of each run is the same as that of a single threaded run, and if the values of other variables are the same as expected. In general, global variables are thread-safe if each thread has only read and no write operations on them. When multiple threads are writing (changing variables) at the same time, thread synchronization is generally considered, otherwise thread safety may be compromised.
- Thread synchronization: it can be understood that thread A and thread B cooperate together. When A is executed to A certain extent, it depends on some result of thread B, so it stops to indicate that thread B is running. B does what he says and gives the result to A; A Continue the operation.
Example “Simulated Train ticket Sale”
Next, we simulate the way to sell train tickets to achieve NSOperation thread safety and solve the thread synchronization problem. Scene: There are 50 train tickets in total, and there are two Windows for selling train tickets, one for Beijing and the other for Shanghai. Train tickets will be sold at both Windows until all tickets are sold out.
/** * Non-thread safe: initialize the number of train tickets, ticket window (non-thread safe), and start selling tickets */ - (void)initTicketStatusNotSave {NSLog(@)"currentThread---%@",[NSThread currentThread]); Self.ticketsurpluscount = 50; self.ticketSurplusCount = 50; NSOperationQueue *queue1 = [[NSOperationQueue alloc] init]; // 1. queue1.maxConcurrentOperationCount = 1; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // 2. queue2.maxConcurrentOperationCount = 1; Op1 __weakTypeof (self) weakSelf = self; // 3. NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ [weakSelf saleTicketNotSafe]; }]; Op2 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{[weakSelf saleTicketNotSafe];}]; [queue1 addOperation:op1]; // 5. [queue2 addOperation:op2]; } /** * Sell train tickets (not thread safe) */ - (void)saleTicketNotSafe {while (1) {
if(self.ticketSurplusCount > 0) {self.ticketSurplusCount > 0; NSLog(@"% @", [NSString stringWithFormat:@"Remaining votes :% D Window :%@", self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval: 0.2]; }else {
NSLog(@"All the train tickets have been sold out");
break; }}}Copy the code
- As you can see, without considering thread safety, without using NSLock, the number of votes is unhinged, which clearly does not meet our requirements, so we need to consider thread safety.
Thread-safe solution: It is possible to lock 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, dispatch_Semaphore, OSSpinLock, Set/GE and so on. Here we use the NSLock object to solve the thread synchronization problem. NSLock objects can be made thread-safe by calling the lock method when the lock is entered and the unlock method when the lock is unlocked.
/** * Thread safe: use NSLock to lock * initialize the number of tickets, ticket window (thread safe), and start selling tickets */ - (void)initTicketStatusSave {NSLog(@)"currentThread---%@",[NSThread currentThread]); Self.ticketsurpluscount = 50; self.ticketSurplusCount = 50; self.lock = [[NSLock alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue1.maxConcurrentOperationCount = 1; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // 2. queue2.maxConcurrentOperationCount = 1; Op1 __weakTypeof (self) weakSelf = self; // 3. NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ [weakSelf saleTicketSafe]; }]; Op2 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{[weakSelf saleTicketSafe];}]; [queue1 addOperation:op1]; // 5. [queue2 addOperation:op2]; } /** * Sell train tickets (thread safe) */ - (void)saleTicketSafe {while(1) {// lock [self.lock];if(self.ticketSurplusCount > 0) {self.ticketSurplusCount > 0; 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 the train tickets have been sold out");
break; }}}Copy the code
- We can see that: in consideration of thread safety, using NSLock lock, unlock mechanism, the number of votes is correct, there is no confusion. We have solved the problem of multiple threads synchronizing.
10.1 NSOperation Common properties and methods
- Cancel operation method
- (void)cancel;
The operation can be cancelled. In essence, it is marked “isCancelled”.
- Method to determine the operation status
- (BOOL)isFinished;
Determine whether the operation is complete.- (BOOL)isCancelled;
Determines whether the operation has been marked as cancelled.- (BOOL)isExecuting;
Determines whether the operation is running.- (BOOL)isReady;
Determines whether the operation is in the ready state. This value is related to the dependencies of the operation.
- Synchronous operation
- (void)waitUntilFinished;
Blocks the current thread until the operation is complete. Can be used for synchronization of the execution order of threads.- (void)setCompletionBlock:(void (^)(void))block;
completionBlock
The completionBlock is executed when the current operation completes.- (void)addDependency:(NSOperation *)op;
Adds a dependency that makes the current operation dependent on the completion of the operation OP.- (void)removeDependency:(NSOperation *)op;
Remove the dependence of the current operation on the op.@property (readonly, copy) NSArray<NSOperation *> *dependencies;
An array of all operation objects that completed execution before the current operation began.
10.2 Common properties and Methods of NSOperationQueue
- Cancel/suspend/resume the operation
- (void)cancelAllOperations;
You can cancel all operations on the queue.- (BOOL)isSuspended;
Determine 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 the operation, YES for suspending the queue, NO for resuming the queue.
- Synchronous operation
- (void)waitUntilAllOperationsAreFinished;
Blocks the current thread until all operations in the queue have completed.
- Add/Get operations
- (void)addOperationWithBlock:(void (^)(void))block;
Adds an operation object of type NSBlockOperation to the queue.- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
Adds an array of operations to the queue with the wait flag whether to block the current thread until all operations have finished- (NSArray *)operations;
Array of operations currently in the queue (automatically cleared from this array when an operation finishes).- (NSUInteger)operationCount;
Operands in the current queue.
- Access to the queue
+ (id)currentQueue;
Gets the current queue or nil if the current thread is not running on NSOperationQueue.+ (id)mainQueue;
Get the main queue.
Note:
- Pauses and cancellations (including cancellations of actions and queues) do not mean that the current action can be cancelled immediately, but that no new action can be performed once the current action has completed.
- The difference between pausing and canceling is that you can resume the operation after you have paused it. When the operation is cancelled, all the operations are cleared and no further operations can be performed.