NSOperation for iOS Multithread Development - Get on board and have no time to explain!

First, what is NSOperation?

NSOperation is a multithreaded solution offered by Apple. In fact, NSOperation is based on a higher level of encapsulation of GCD, but it is more object-oriented, more readable and more controllable than GCD. It is very important to add operation dependency.

By default, NSOperations can only perform operations synchronously when they are used alone, without the ability to create new threads, and only with NSOperationQueue can asynchronous execution be achieved. At this point, we can easily find that GCD and NSOperation are implemented in a very similar way. Actually, this is more like nonsense. NSOperation itself is based on the encapsulation of GCD. NSOperation is equivalent to tasks in GCD, while NSOperationQueue is equivalent to queues in GCD.< GCD for iOS Multithread Development (Part I) > The essence of GCD has been elaborated: All developers have to do is define the tasks they want to perform and append them to the appropriate Dispatch Queue. So we can also say that the essence of NSOperations is to define the tasks that you want to perform and add them to the appropriate NSOperationQueue.

 

II. Use of NSOperation

Creating tasks

NSOperation is an abstract base class representing an independent computing unit that provides useful and thread-safe setup state, priority, dependency and cancellation operations for subclasses. But it can't be used to encapsulate tasks directly. It can only be encapsulated by its subclasses. Generally, we can use NSBlockOperation, NSInvocation Operation or define subclasses inherited from NSOperation to encapsulate tasks by implementing internal corresponding methods.

   (1)NSBlockOperation

- (void)invocationOperation{

    NSLog(@"start - %@",[NSThread currentThread]);
    
    // Establish NSInvocationOperation object
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];
    
    // call start Method to start the operation
    [op start];
    
    NSLog(@"end - %@",[NSThread currentThread]);
}

- (void)testRun{
    NSLog(@"invocationOperation -- %@", [NSThread currentThread]);
}

Implementation results:

2017-07-14 13:43:59.327 beck.wang[10248:1471363] start - <NSThread: 0x6100000614c0>{number = 1, name = main}
2017-07-14 13:43:59.328 beck.wang[10248:1471363] invocationOperation -- <NSThread: 0x6100000614c0>{number = 1, name = main}
2017-07-14 13:43:59.328 beck.wang[10248:1471363] end - <NSThread: 0x6100000614c0>{number = 1, name = main}

Analysis: In the case of using NSInvocation Operation alone, NSInvocation Operation performs operations synchronously in the main thread and does not open new threads.

  (2)NSBlockOperation

- (void)blockOperation{
    
    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"blockOperation--%@", [NSThread currentThread]);
    }];
    
    NSLog(@"end - %@",[NSThread currentThread]);
    
    [op start];
}

Print results:

2017-07-14 13:49:25.436 beck.wang[10304:1476355] start - <NSThread: 0x6100000653c0>{number = 1, name = main}
2017-07-14 13:49:25.436 beck.wang[10304:1476355] end - <NSThread: 0x6100000653c0>{number = 1, name = main}
2017-07-14 13:49:25.436 beck.wang[10304:1476355] blockOperation--<NSThread: 0x6100000653c0>{number = 1, name = main}

Analysis: In the case of using NSBlockOperation alone, NSBlockOperation also executes operations on the main thread without opening new threads.

It's worth noting that NSBlockOperation also provides a method called addExecution Block:, through which additional operations can be added to NSBlockOperation, which will be executed concurrently in other threads.

- (void)blockOperation{
    
    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"blockOperation--%@", [NSThread currentThread]);
    }];
    
    // Adding additional tasks(Execution in sub-threads)
    [op addExecutionBlock:^{
        NSLog(@"addTask1---%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"addTask2---%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"addTask3---%@", [NSThread currentThread]);
    }];
    
    NSLog(@"end - %@",[NSThread currentThread]);
    
    [op start];
}

Print results:

2017-07-14 13:57:02.009 beck.wang[10351:1482603] start - <NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.009 beck.wang[10351:1482603] end - <NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.010 beck.wang[10351:1482603] blockOperation--<NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.010 beck.wang[10351:1482642] addTask1---<NSThread: 0x618000260e00>{number = 3, name = (null)}
2017-07-14 13:57:02.010 beck.wang[10351:1482645] addTask3---<NSThread: 0x600000263200>{number = 5, name = (null)}
2017-07-14 13:57:02.010 beck.wang[10351:1482643] addTask2---<NSThread: 0x610000264600>{number = 4, name = (null)}

Analysis: The blockOperationWithBlock task is executed in the main thread, and the addExecutionBlock task is executed in the new thread.

   

(3) Customize NSOperation subclass - override main method

    .h

@interface ZTOperation : NSOperation

@end

    .m

@implementation ZTOperation

- (void)main{

    // Here you can customize tasks
    NSLog(@"ZTOperation--%@",[NSThread currentThread]);
}
@end

    ViewController

ZTOperation *zt = [[ZTOperation alloc] init];
[zt start];

Print results:

2017-07-14 14:05:58.824 beck.wang[10389:1490955] ZTOperation--<NSThread: 0x60000007a940>{number = 1, name = main}

Analysis: Tasks are executed in the main thread without opening new threads.

 

2. Creating queues

NSOperationQueue has two kinds of queues: primary queue and other queues. Other queues include both serial and concurrent functions. Serial and concurrent can be achieved by setting the maximum number of concurrent maxConcurrentOperationCount.

(1) Main Queue -- Tasks are executed in the main thread

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

(2) Other queues -- tasks are executed in sub-threads

NSOperationQueue *elseQueue = [[NSOperationQueue alloc] init];

 

3. NSOperation + NSOperationQueue

// Add a single operation:
 - (void)addOperation:(NSOperation *)op;

// Add multiple operations:
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);

// Add to block Operation:
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

Code examples:

- (void)addOperationToQueue
{
    
    NSLog(@"start - %@",[NSThread currentThread]);
    
    // Create queues
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // Establish NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];
    
    // Establish NSBlockOperation
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task002 -- %@", [NSThread currentThread]);
    }];
    
    // Add operations to the queue: addOperation:
    [queue addOperation:op1];
    [queue addOperation:op2];
    
    // Add operations to the queue: addOperationWithBlock:
    [queue addOperationWithBlock:^{
        NSLog(@"task003-----%@", [NSThread currentThread]);
    }];
    
    NSLog(@"end - %@",[NSThread currentThread]);
}

- (void)testRun{
    NSLog(@"task001 -- %@", [NSThread currentThread]);
}

Print results:

2017-07-14 14:39:51.669 beck.wang[10536:1516641] start - <NSThread: 0x610000077640>{number = 1, name = main}
2017-07-14 14:39:51.670 beck.wang[10536:1516641] end - <NSThread: 0x610000077640>{number = 1, name = main}
2017-07-14 14:39:51.670 beck.wang[10536:1516686] task003-----<NSThread: 0x600000077200>{number = 3, name = (null)}
2017-07-14 14:39:51.670 beck.wang[10536:1516689] task002 -- <NSThread: 0x61800007e080>{number = 5, name = (null)}
2017-07-14 14:39:51.670 beck.wang[10536:1516687] task001 -- <NSThread: 0x61000007e1c0>{number = 4, name = (null)}

Analysis: Open new threads and execute concurrently.

 

Management of NSOperationQueue

1. Queue Cancellation, Suspension, Recovery

- (void)cancel; (NSOperation provides a method to cancel a single operation

- (void)cancelAllOperations; (NSOperationQueue provides methods to cancel all operations on queues

- (void)setSuspended:(BOOL)b; (pause and recovery of tasks can be set; YES stands for pause queue; NO stands for recovery queue

- (BOOL) is Suspended; (BOOL) Judging pause status

Suspension or cancellation does not immediately suspend or cancel an operation being performed, but does not cause a new operation to be executed after the current operation has been executed. The difference between the two is that the operation can be resumed after the suspension of the operation and continue to be performed downward; and after the cancellation of the operation, all the operations are empty and the remaining operations cannot be performed again.

 

Maximum concurrency number maxConcurrent OperationCount

MaxConcurrent OperationCount=-1 means no restriction, default concurrent execution;

MaxConcurrent OperationCount = 1 indicates that the maximum concurrency is 1, and serial execution is performed.

MaxConcurrent OperationCount >([count] >= 1) means concurrent execution, min[count, system limitation].

Code examples:

- (void)operationQueue
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // Setting maximum concurrent operands
    // queue.maxConcurrentOperationCount = - 1;  // Concurrent execution
    // queue.maxConcurrentOperationCount = 1; // Synchronized execution
     queue.maxConcurrentOperationCount = 2; // Concurrent execution
    
    [queue addOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task3-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task4-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task5-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task6-----%@", [NSThread currentThread]);
    }];
}

Print results:

// queue.maxConcurrentOperationCount = - 1

2017-07-14 15:28:39.554 beck.wang[10772:1557342] task2-----<NSThread: 0x61800006d340>{number = 4, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557358] task3-----<NSThread: 0x6080000751c0>{number = 5, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557359] task4-----<NSThread: 0x610000071c00>{number = 6, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557339] task5-----<NSThread: 0x60000006ea40>{number = 7, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557340] task1-----<NSThread: 0x608000073500>{number = 3, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557360] task6-----<NSThread: 0x610000071c80>{number = 8, name = (null)}

// Analysis: 6 threads, concurrent execution

-----------------------------------Partition line----------------------------------------------

// queue.maxConcurrentOperationCount =  1

2017-07-14 15:27:04.365 beck.wang[10743:1555231] task1-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task2-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task3-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task4-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.366 beck.wang[10743:1555231] task5-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.366 beck.wang[10743:1555231] task6-----<NSThread: 0x60800007c880>{number = 3, name = (null)}

// Analysis: Number of threads is 1, synchronous execution

-----------------------------------Partition line----------------------------------------------

// queue.maxConcurrentOperationCount =  2

2017-07-14 15:18:26.162 beck.wang[10715:1548342] task2-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548344] task1-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548342] task4-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548344] task3-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548342] task5-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.163 beck.wang[10715:1548344] task6-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}

// Analysis: 2 threads, concurrent execution

Obviously, by setting up maxConcurrent OperationCount, can concurrent and serial functions be realized much easier than GCD!?

     

3. Operational dependency

In NSOperations, we can decompose operations into several small tasks, which are often used by adding dependencies between them. This is also the attraction of NSOperation. It doesn't need to be implemented with complex code like GCD, and addDependency can do it!

- (void)addDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"task1-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task3-----%@", [NSThread  currentThread]);
    }];
    
    // op2 Dependence on op1 Execution order op1->op2 Must be placed in[Add operation queue]before
    [op2 addDependency:op1];
    
    // Avoid cyclic dependence op2 Has relied on op1,Never let it go again op1 Dependence on op2,Forming cyclic dependence
    //[op1 addDependency:op2];
    
    // Add operation queue
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}
Print results:
2017-07-14 15:46:02.011 beck.wang[10854:1571574] task3-----<NSThread: 0x61800006d740>{number = 3, name = (null)}
2017-07-14 15:46:04.085 beck.wang[10854:1571596] task1-----<NSThread: 0x60000006f040>{number = 4, name = (null)}
2017-07-14 15:46:04.085 beck.wang[10854:1571574] task2-----<NSThread: 0x61800006d740>{number = 3, name = (null)}

Analysis: task2 must be executed after task1, because the thread is set to wait for 2s before Task1 is executed, and all task3 is executed earliest.

 

4. Operational priority

NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

   

5. Operation Monitoring

You can monitor whether an operation has been completed. To download the picture below, you need to download the first picture before downloading the second picture. Here you can set up the monitor.

- (void)addListing{

    NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    
    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
        for (int i=0; i<3; i++) {
            NSLog(@"Download Picture 1-%@",[NSThread currentThread]);
        }
    }];
    
    // Completion of the monitoring operation
    operation.completionBlock=^{
        // Continue to download pictures
        NSLog(@"--Download Picture 2--");
    };
   
    [queue addOperation:operation];
}

Implementation results:

2017-07-14 16:21:43.833 beck.wang[10930:1597954] Download Picture 1 - <NSThread: 0x61800007a340> {number = 3, name = null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597954] Download Picture 1 - <NSThread: 0x61800007a340> {number = 3, name = null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597954] Download Picture 1 - <NSThread: 0x61800007a340> {number = 3, name = null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597955] -- Download picture 2--

Analysis: Picture 2 will be downloaded after downloading Picture 1, which is similar to the Addition Dependency in Knowledge Point 3.

 

In the final words: Multithreading is not just GCD! If you haven't used NSOperation yet, what else do you say? Practice quickly! Of course, they have their own usage scenarios, which is reasonable if they exist! The three technologies of iOS multithreading, GCD, NSThread and NSOperation, are all introduced. We need to know that GCD and NSThread can go back to my previous blog.  

   

Keywords: iOS

Added by BadgerC82 on Thu, 13 Jun 2019 22:11:45 +0300