Multi-threaded development is an indispensable part of daily development tasks. Multi-threaded development technologies commonly used in iOS development include GCD, NSOperation and NSThread. This paper mainly explains the knowledge and detailed usage of NSOperation in multi-line series articles.

  1. IOS multithreading: GCD details
  2. IOS multithreading: NSOperation details

1. Introduction to NSOperation

NSOperation is a complete multithreading solution provided by Apple. In fact, it is based on a higher level of ENCAPSULATION of GCD and is completely object-oriented. Much simpler to use and more readable code than GCD. NSOperation is well used in many multi-threaded tasks, including network requests and image compression. Of course, NSOperation also requires the important role of NSOperationQueue.

  1. Supports dependency relationships between operation objects, facilitating the control of execution sequence.
  2. Support for optional completion blocks, which are executed after the main task of the operation is complete.
  3. Support for using KVO to notify changes in the execution status of monitoring operations.
  4. Support for prioritizing operations to influence their relative order of execution.
  5. Cancel action is supported, allowing you to pause an action while it is executing.

NSOperation task and queue

2.1. NSOperation Task

Like GCD, NSOperation also has the concept of a task. A task is a piece of code that is executed in a thread. In GCD, it is executed in a block. In NSOperation, it is executed in its subclasses NSInvocationOperation, NSBlockOperation, and custom subclasses. Different from GCD, NSOperation requires the cooperation of NSOperationQueue to achieve multi-threading. When NSOperation is used alone, it performs operations synchronously, and asynchronously with NSOperationQueue.

2.2 NSOperation queue

The queue in NSOperation is represented by NSOperationQueue, which is used to store the queue for the task.

  • Different from the queue first-in, first-out principle in GCD, for the tasks added to the NSOperationQueue queue, the ready state of the tasks is determined according to the dependencies between the tasks, and then the ready state of the tasks is determined by the relative priority of the tasks.
  • NSOperationQueue also provides a way to set the maximum number of concurrent tasks.
  • NSOperationQueue also provides 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. Basic use of NSOperation

NSOperation is an abstract class that must be subclassed in order to do any useful work. Although this class is abstract, it gives its subclasses a very useful and thread-safe way to model states, priorities, dependencies, cancellations, and so on. NSOperation provides three ways to create tasks. 1. Subclass NSInvocationOperation; Use subclass NSBlockOperation; 3. Customize subclasses inherited from NSOperation to encapsulate operations by implementing internal corresponding methods.

Now let’s take a look at the three different ways of using NSOperation separately.

3.1, NSInvocationOperation

The NSInvocationOperation class is a concrete subclass of NSOperation that, when run, calls the specified method on the specified object. Using this class avoids the need to define a large number of custom action objects for each task in your application.

-(void)invocationOperation{
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
    [operation start];
}

-(void)operation{
    for (int i = 0; i < 5; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"%d--%@",i,[NSThread currentThread]); }}Copy the code

2020-03-19 17:09:46.189458+0800 ThreadDemo[44995:12677738] 0–

{number = 1, name = main} 2020-03-19 17:09:48.190629+0800 ThreadDemo[44995:12677738] 1–

{number = 1, name = main} 2020-03-19 17:09:50.191219+0800 ThreadDemo[44995:12677738] 2–

{number = 1, name = main} 2020-03-19 17:09:52.192556+0800 ThreadDemo[44995:12677738] 3–

{number = 1, name = main} 2020-03-19 17:09:54.193900+0800 ThreadDemo[44995:12677738] 4–

{number = 1, name = main}




As the result of the above code run shows, when NSInvocationOperation is used alone, no new thread is started and the tasks are executed in the current thread.

3.2, NSBlockOperation

The NSBlockOperation class is a concrete subclass of NSOperation that acts as a wrapper around one or more block objects. This class provides an object-oriented wrapper for applications that already use action queues and do not want to create dispatch queues. You can also use block operations to take advantage of operation dependencies, KVO notifications, and other features that may not be available with scheduling queues.

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

2020-03-19 17:19:38.673513+0800 ThreadDemo[45160:12689966] 0–

{number = 1, name = main} 2020-03-19 17:19:40.675074+0800 ThreadDemo[45160:12689966] 1–

{number = 1, name = main} 2020-03-19 17:19:42.676649+0800 ThreadDemo[45160:12689966] 2–

{number = 1, name = main} 2020-03-19 17:19:44.677073+0800 ThreadDemo[45160:12689966] 3–

{number = 1, name = main} 2020-03-19 17:19:46.677379+0800 ThreadDemo[45160:12689966] 4–

{number = 1, name = main}




As shown in the results of the above code run, when NSBlockOperation is used alone, no new thread is started and the execution of the task is performed in the current thread.

There is also an addExecutionBlock method provided in the NSBlockOperation class, which adds a code execution block that submits all of its blocks to a concurrent scheduling queue of default priority when an NSBlockOperation object needs to be executed. The object then waits until all blocks have finished executing. When the last block completes execution, the action object marks itself as completed. Thus, we can use block operations to track a set of executing blocks, much like using thread joins to merge results from multiple threads. The difference is that because the block operation itself runs on a separate thread, other threads of the application can continue working while waiting for the block operation to complete. It is important to note that these operations (including those in blockOperationWithBlock) may be executed concurrently in different threads, depending on the system, if more tasks are added.

- (void)blockOperationDemo {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"blockOperation--%@", [NSThread currentThread]); }}]; [operation addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"executionBlock1--%@", [NSThread currentThread]); }}]; [operation addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"executionBlock2--%@", [NSThread currentThread]); }}]; [operation addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"executionBlock3--%@", [NSThread currentThread]); }}]; [operation addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"executionBlock4--%@", [NSThread currentThread]); }}]; [operation addExecutionBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"executionBlock5--%@", [NSThread currentThread]); }}]; [operation start]; }Copy the code

ThreadDemo[45536:12708941] executionBlock4–

{number = 1, Name = main} 2020-03-19 17:40:08.102555+0800 ThreadDemo[45536:12709185] 0x600002a57b80>{number = 8, Name = (null)} ThreadDemo[45536:12709191] executionBlock5–

{number = 9, Name = (null)} 2020-03-19 17:40:08.102566+0800 ThreadDemo[45536:12709186] executionBlock3–

{number = 4, Name = (null)} 2020-03-19 17:40:08.102570+0800 ThreadDemo[45536:12709184] executionBlock1–

{number = 6, Name = (null)} 2020-03-19 17:40:08.102576+0800 ThreadDemo[45536:12709187] blockOperation–

{number = 5, Name = (null)} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709187] blockOperation–

{number = 5, Name = (null)} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12708941] 0x600002a1ab00>{number = 1, Name = main} 2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709185] 0x600002a57b80>{number = 8, Name = (null)} ThreadDemo[45536:12709191] executionBlock5–

{number = 9, Name = (null)} ThreadDemo[45536:12709186] executionBlock3–

{number = 4, ThreadDemo[45536:12709184] executionBlock1–

{number = 6, name = (null)}








As shown in the code execution results above, after the addExecutionBlock method is called to add more than one group of tasks, a new thread is started. The tasks are executed concurrently, and the execution of the tasks in blockOperationWithBlock is not executed on the current thread.

3.3. User-defined subclasses of NSOperation

If subclasses NSInvocationOperation and NSBlockOperation do not meet your daily needs, you can also customize subclasses. Make a class that inherits from NSOperation and override its main or start methods.

@interface CustomerOperation : NSOperation

@end

@implementation CustomerOperation
- (void)main{
    if(! self.isCancelled){for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d--%@",i,[NSThread currentThread]);
        }
    }
}

-(void)customerOperation{
    CustomerOperation *operation = [[CustomerOperation alloc]init];
    [operation start];
}
Copy the code

2020-03-19 20:28:54.473676+0800 ThreadDemo[47267:12811915] 0–

{number = 1, name = main} 2020-03-19 20:28:56.474363+0800 ThreadDemo[47267:12811915] 1 0x600001289040>{number = 1, name = main} 2020-03-19 20:28:58.474708+0800 ThreadDemo[47267:12811915] 2 0x600001289040>{number = 1, name = main} 2020-03-19 20:29:00.476058+0800 ThreadDemo[47267:12811915] 3–

{number = 1, name = main}

As can be seen from the above code running result, the custom Operation does not start a new thread, and the task is executed in the current thread.

Now let’s see how NSOperationQueue is used in conjunction with NSOperation.

3.4. Add a task to the queue

As mentioned above, NSOperation requires NSOperationQueue to be used in conjunction with multi-threading. So we need to load the created NSOperation object into the NSOperationQueue queue. NSOperationQueue provides two types of queues: primary queue and custom queue. The custom queue contains two different functions: serial queue and concurrent queue.

  • Main queue: Pass[NSOperationQueue mainQueue]Any tasks added to the main queue are executed in the main thread.
  • Custom queue: Pass[[NSOperationQueue alloc] init]All tasks added to the custom queue are automatically put into child threads for execution.

3.4.1 track, addOperation

Call the addOperation method to add the created Operation object to the created queue.

- (void)operationQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//    NSOperationQueue *queue = [NSOperationQueue mainQueue];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation1--%@", [NSThread currentThread]); }}]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation2--%@", [NSThread currentThread]); }}]; [queue addOperation:operation1]; [queue addOperation:operation2]; }Copy the code

ThreadDemo[47889:12843365] operation1–

{number = 3, Name = (NULL)} 2020-03-19 21:01:45.868610+0800 ThreadDemo[47889:12843364] operation2–

{number = 6, Name = (null)} 2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843365] operation1 0x6000012cd900>{number = 3, Name = (null)} 2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843364] operation2– 0x6000012e0640>{number = 6, name = (null)}

As you can see from the results of the above code run, a new thread is started and the task is executed concurrently. If you replace queue with mainQueue, the task will be executed synchronously in the main thread.

3.4.2, addOperations

If many tasks are too cumbersome to add to the queue one by one, addOperations comes into play.

- (void)operationQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//    NSOperationQueue *queue = [NSOperationQueue mainQueue];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation1--%@", [NSThread currentThread]); }}]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation2--%@", [NSThread currentThread]); }}]; NSArray *operationList = @[operation1,operation2]; [queue addOperations:operationListwaitUntilFinished:NO];
    NSLog(@"end");
}
Copy the code

Print result: 2020-03-19 21:06:30.381594+0800 ThreadDemo[48047:12849411] end 2020-03-19 21:06:32.385653+0800 ThreadDemo[48047:12849496] operation1–

{number = 8, Name = (NULL)} 2020-03-19 21:06:32.385651+0800 ThreadDemo[48047:12849498] operation2–

{number = 4, Name = (NULL)} 2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849496] operation1–

{number = 8, Name = (NULL)} 2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849498] operation2–

{number = 4, name = (null)}



As you can see from the above code runs, a new thread is started and the task is executed concurrently. If you replace queue with mainQueue, the task will be executed synchronously in the main thread.

One thing to note here is the waitUntilFinished parameter. If you pass YES, it will wait for the task in the queue to complete before proceeding, which blocks the thread.

Rule 3.4.3, addOperationWithBlock

NSOperationQueue also provides an addOperationWithBlock method to add an operation object to the NSOperationQueue.

-(void)addOperationWithBlock{
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//        NSOperationQueue *queue = [NSOperationQueue mainQueue];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation1--%@", [NSThread currentThread]); }}]; [queue addOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation2--%@", [NSThread currentThread]); }}]; }Copy the code

ThreadDemo[48192:12856146] operation1–

{number = 4, Name = (null)} 2020-03-19 21:11:54.069593+0800 ThreadDemo[48192:12856148] operation2–

{number = 3, Name = (null)} 2020-03-19 21:11:56.070432+0800 ThreadDemo[48192:12856148] operation2–

{number = 3, Name = (null)} 2020-03-19 21:11:56.070430+0800 ThreadDemo[48192:12856146] operation1–

{number = 4, name = (null)}



As you can see from the above code runs, a new thread is started and the task is executed concurrently. If you replace queue with mainQueue, the task will be executed synchronously in the main thread.

3.5. Synchronous execution & Concurrent execution

As mentioned earlier, NSOperation alone is executed synchronously by the system by default. If you need to execute tasks concurrently, you need the cooperation of NSOperationQueue. So decision is the key to the concurrent execution or synchronous execution is the maximum number of concurrent tasks maxConcurrentOperationCount.

  • By defaultmaxConcurrentOperationCountThe value of is -1, which is not restricted and can be executed concurrently, as in NSBlockOperation, which adds multiple task blocks.
  • maxConcurrentOperationCountWhen the value of is 1, synchronization is performed.
  • maxConcurrentOperationCountIs executed concurrently if the value is greater than 1.
  • maxConcurrentOperationCountThe value does not represent the number of threads executing concurrently, but rather the number of tasks that can be executed simultaneously in a queue.
- (void)maxConcurrentOperationCount { NSOperationQueue *queue = [[NSOperationQueue alloc]init]; queue.maxConcurrentOperationCount = 1; / / / / serial queue queue. MaxConcurrentOperationCount = 4; // Concurrent queue NSLog(@"maxCount=%ld", (long)queue.maxConcurrentOperationCount);
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation1--%@", [NSThread currentThread]); }}]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation2--%@", [NSThread currentThread]); }}]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"operation3--%@", [NSThread currentThread]); }}]; NSArray *operationList = @[operation1, operation2, operation3]; [queue addOperations:operationListwaitUntilFinished:YES];

    NSLog(@"end");
}
Copy the code

Print result: 2020-03-19 21:35:43.882396+0800 ThreadDemo[48619:12879620] maxCount=1 2020-03-19 21:35:43.882396+0800 ThreadDemo[48619:12879824] operation1–

{number = 3, Name = (NULL)} 2020-03-19 21:35:45.882889+0800 ThreadDemo[48619:12879824] operation1–

{number = 3, Name = (NULL)} 2020-03-19 21:35:47.886984+0800 ThreadDemo[48619:12879824] operation2–

{number = 3, Name = (NULL)} 2020-03-19 21:35:49.888093+0800 ThreadDemo[48619:12879824] operation2–

{number = 3, Name = (NULL)} 2020-03-19 21:35:51.893354+0800 ThreadDemo[48619:12879824] operation3–

{number = 3, Name = (NULL)} 2020-03-19 21:35:53.894355+0800 ThreadDemo[48619:12879824] operation3–

{Number = 3, Name = (NULL)} 2020-03-19 21:35:53.894723+0800 ThreadDemo[48619:12879620] end





As you can see from the above code results, a new thread is started and the task is executed serially.

If amend the maxConcurrentOperationCount value to 2, then print the result is as follows:

2020-03-19 21:37:01.130238+0800 ThreadDemo[48668:12881702] maxCount=2 2020-03-19 21:37:01.130238+0800 ThreadDemo[48668:12881793] operation1–

{number = 5, Name = (NULL)} 2020-03-19 21:37:01.130246+0800 ThreadDemo[48668:12881794] operation2–

{number = 6, Name = (NULL)} 2020-03-19 21:37:03.133480+0800 ThreadDemo[48668:12881793] operation1–

{number = 5, Name = (NULL)} 2020-03-19 21:37:03.133489+0800 ThreadDemo[48668:12881794] operation2–

{number = 6, Name = (NULL)} 2020-03-19 21:37:05.137502+0800 ThreadDemo[48668:12881794] operation3–

{number = 6, Name = (NULL)} 2020-03-19 21:37:07.140419+0800 ThreadDemo[48668:12881794] operation3–

{Number = 6, Name = (NULL)} 2020-03-19 21:37:07.140713+0800 ThreadDemo[48668:12881702] end





Run results can be seen from the above, open a new thread, task is concurrent execution, and each task for maximum 2, it is because we set the maxConcurrentOperationCount value of 2, and add the three tasks in the queue.

3.6 Communication between NSOperation threads

Multithreading may never get around the topic of interthread communication. Usually, time-consuming operations such as network requests and file upload and download are executed in the sub-thread. After the execution is completed, the user needs to return to the main thread for UI refresh. Therefore, there will be a problem of switching between the main thread and the sub-thread.

-(void)threadCommunication{
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"Child thread --%@", [NSThread currentThread]);
        }
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"Main thread --%@", [NSThread currentThread]); }}]; }]; [queue addOperation:operation]; }Copy the code

2020-03-19 21:48:12.051256+0800 ThreadDemo[48922:12893188]

{Number = 6, name = (null)} 2020-03-19 21:48:14.056107+0800 ThreadDemo[48922:12893188] child thread —

{Number = 6, name = (null)} 2020-03-19 21:48:16.059279+0800 ThreadDemo[48922:12893188] child thread —

{Number = 6, name = (null)} 2020-03-19 21:48:18.062773+0800 ThreadDemo[48922:12893188] 0x600000B5FA80 >{Number = 6, name = (null)} 2020-03-19 21:48:20.064401+0800 ThreadDemo[48922:12893108] 0x600000BD2D00 >{number = 1, name = main} 2020-03-19 21:48:22.065409+0800 ThreadDemo[48922:12893108] 0x600000bd2d00>{number = 1, name = main}


3.7 NSOperation Operation dependency

The best thing about NSOperation is that you can add dependencies between tasks. A dependency is when task A waits for task B to complete. NSOperation provides three methods for setting dependencies between tasks.

  • -(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.
  • NSArray<NSOperation *> *dependencies;Array of all action objects that are executed before the current operation begins.
- (void)addDependency {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1 - % @", [NSThread currentThread]); }}]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2 - % @", [NSThread currentThread]); }}]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3 - % @", [NSThread currentThread]); }}]; // Operation1 depends on operation2 and operation3, then operation1 [operation1 addDependency:operation2]; [operation1 addDependency:operation3]; NSArray *opList = @[operation1,operation2,operation3]; NSArray *dependencies = [operation1 dependencies]; NSLog(@"dependencies-%@",dependencies);
    [queue addOperations:opList waitUntilFinished:YES];
    NSLog(@"end");
}
Copy the code

2020-03-19 22:11:32.567850+0800 ThreadDemo[49369:12918472] 0x7ff341a06e40>”, ”

“) 2020-03-19 22:11:34.571689+0800 ThreadDemo[49369:12918726] 3–

{Number = 3, name = (null)} 2020-03-19 22:11:34.571694+0800 ThreadDemo[49369:12918732] 2–

{Number = 7, name = (null)} 2020-03-19 22:11:36.577098+0800 ThreadDemo[49369:12918726] 3–

{Number = 3, name = (null)} 2020-03-19 22:11:36.577107+0800 ThreadDemo[49369:12918732] 2–

{Number = 7, name = (null)} 2020-03-19 22:11:38.582249+0800 ThreadDemo[49369:12918726] 1–

{Number = 3, name = (null)} 2020-03-19 22:11:40.587676+0800 ThreadDemo[49369:12918726] 1–

{Number = 3, Name = (NULL)} 2020-03-19 22:11:40.587996+0800 ThreadDemo[49369:12918472] end






Operation1 is executed after OPERATION2 and Operation3 are completed, as shown in the code results above.

3.8 Priority of NSOperation

Another highlight of NSOperation is that NSOperation provides the queuePriority property, which determines the order in which tasks are executed in the queue.

  • queuePriorityAttribute is valid only for tasks in the same queue.
  • queuePriorityAttributes do not replace dependencies.
  • High-priority tasks take precedence over low-priority tasks for tasks that enter the ready state.
  • High-priority tasks are not necessarily executed first, because low-priority tasks that have entered the ready state are executed first.
  • The default priority of the newly created Operation object isNSOperationQueuePriorityNormal, can be accessed throughsetQueuePriority:Method to modify the priority.
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
Copy the code
- (void)addDependency {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1 - % @", [NSThread currentThread]); }}]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2 - % @", [NSThread currentThread]); }}]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3 - % @", [NSThread currentThread]); }}]; // Operation1 depends on operation2 and operation3, then operation1 [operation1 addDependency:operation2]; [operation1 addDependency:operation3]; operation1.queuePriority = NSOperationQueuePriorityVeryHigh; NSArray *opList = @[operation1,operation2,operation3]; NSArray *dependencies = [operation1 dependencies]; NSLog(@"dependencies-%@",dependencies);
    [queue addOperations:opList waitUntilFinished:YES];
    NSLog(@"end");
}
Copy the code

2020-03-19 22:31:15.086135+0800 ThreadDemo[49743:12937692] 0x7ffa6140a980>”, ”

“) 2020-03-19 22:31:17.087052+0800 ThreadDemo[49743:12937910] 3–

{Number = 5, name = (null)} 2020-03-19 22:31:17.087060+0800 ThreadDemo[49743:12937909] 2–

{Number = 4, name = (null)} 2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937909] 2–

{Number = 4, name = (null)} 2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937910] 3–

{Number = 5, name = (null)} 2020-03-19 22:31:21.090223+0800 ThreadDemo[49743:12937910] 1–

{Number = 5, name = (null)} 2020-03-19 22:31:23.092879+0800 ThreadDemo[49743:12937910] 1–

{Number = 5, Name = (NULL)} 2020-03-19 22:31:23.093183+0800 ThreadDemo[49743:12937692] end






, as shown in the above code to run the result even if priority is set to the highest NSOperationQueuePriorityVeryHigh operation1, operation1 remains the last, That is because Operation1 depends on Operation2 and Operation3. Operation1 is in the ready state until Operation2 and Operation3 are completed and will not be executed, even if it has the highest priority.

3.9, state,

NSOperation contains a very elegant state machine that describes the execution of each operation. IsReady → isExecuting → isFinished. In place of the less explicit state attribute, the state is determined directly by those keypath KVO notifications above, that is, when an operation isReady to be performed, it sends a KVO notification to isReady’s keypath, Make the keypath property isReady return YES when accessed. Each attribute must be independent of the other attributes, that is, only one attribute can return YES at the same time to maintain a continuous state:

  • isReadyReturns theYESIndicates that the operation is ready to be executed, if returnedNOIt indicates that there are other related steps that have not been completed.
  • isExecutingReturns theYESIndicates that the operation is being performed. Otherwise, the operation is not being performed.
  • isFinishedReturns theYESIndicates that the operation was successfully executed or cancelled.NSOperationQueueOnly when it manages all operationsisFinishedAll attributes are marked asYESThe operation will stop being published later, that is, the queue will stop running, so implementing this method correctly is critical to avoid deadlocks.

3.10 other apis

  1. - (void)cancel;Operations can be cancelled, essentially marking isCancelled status. Method to determine operation status
  2. - (BOOL)isFinished;Check whether the operation is complete.
  3. - (BOOL)isCancelled;Determines whether the operation has been marked as cancelled.
  4. - (BOOL)isExecuting;Determine whether the operation is running.
  5. - (BOOL)isReady;Determines whether an operation is in a ready state. This value depends on the dependency of the operation.
  6. - (void)waitUntilFinished;Blocks the current thread until the operation is complete. Can be used for sequential synchronization of threads.
  7. - (void)setCompletionBlock:(void (^)(void))block;CompletionBlock executes the completionBlock when the current operation completes.
  8. - (void)cancelAllOperations;You can cancel all operations on the queue.
  9. - (BOOL)isSuspended;Determines whether the queue is paused. YES indicates the pause state, and NO indicates the recovery state. 10.- (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.
  10. - (void)waitUntilAllOperationsAreFinished;Blocks the current thread until all operations in the queue have completed.
  11. - (NSUInteger)operationCount;Operands in the current queue. Access to the queue
  12. + (id)currentQueue;Gets the current queue, and returns nil if the current thread is not running on NSOperationQueue.

4. Thread safety of NSOperation

As with other multithreading solutions, NSOperation can be solved by locking the thread. When 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, etc.

5. Reference materials

  • Apple Official documentation -Operation Queues
  • Apple official documentation -NSOperation
  • NSOperation