Introduction to the

NSOperation implements multithreaded programming

Specific steps

  1. Encapsulate the operations to be performed into an NSOperation object
  2. The NSOperation object is then added to the NSOperationQueue
  3. The system will automatically fetch the NSOperation from the NSOperationQueue
  4. The removed NSOperation encapsulated operations are executed in a new thread

A subclass of NSOperation

NSOperation is an abstract class that does not encapsulate operations. You must use a subclass of NSOperation

There are three ways to use an NSOperation subclass

  • NSInvocationOperation
  • NSBlockOperation
  • Custom subclasses inherit NSOperation to implement the corresponding methods internally

NSInvocationOperation and NSBlockOperation

  1. NSInvocationOperation
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //1. Create a task, Encapsulating task NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task1) object:nil]; //2. Start/execute [op1 start]; [self task1]; } -(void)task1{ NSLog(@"%s----%@",__func__,[NSThread currentThread]); }Copy the code
2021-04-10 11:39:56.589533+0800 NSOperation01- Basic usage [4783:154245] -[ViewController task1]----<NSThread: 0x600002a886c0>{number = 1, Name = main} 2021-04-10 11:39:56.589671+0800 NSOperation01- Basic usage [4783:154245] -[ViewController task1]----<NSThread: 0x600002a886c0>{number = 1, name = main}Copy the code

You can see that the encapsulated task starts the task with the same result as calling the task1 method directly, and is executed in the main thread without starting the child thread, because NSOperation needs to work with NSOperationQueue to start multithreading, and the above example does not use queues

  1. blockOperation
-(void)blockOperation{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    [op1 start];
    [op2 start];
    [op3 start];
}
Copy the code
2021-04-10 11:49:53.951734+0800 NSOperation01- Basic usage [4960:163855] 1----<NSThread: 0x600003b381c0>{number = 1, Name = main} 2021-04-10 11:49:53.951886+0800 NSOperation01- Basic usage [4960:163855] 2----<NSThread: 0x600003b381c0>{number = 1, Name = main} 2021-04-10 11:49:53.952009+0800 NSOperation01- Basic usage [4960:163855] 3----<NSThread: 0x600003b381c0>{number = 1, name = main}Copy the code

You can see that the tasks are executed sequentially in the main thread and that multithreading is not enabled

But we can append tasks to the operation through the addExecutionBlock method of NSBlockOperation, as follows

-(void)blockOperation{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    [op1 addExecutionBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];

    [op2 addExecutionBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"6----%@",[NSThread currentThread]);
    }];
    
    [op1 start];
    [op2 start];
    [op3 start];
}
Copy the code
2021-04-10 11:50:21.339177+0800 NSOperation01- Basic usage [4992:164915] 1----<NSThread: 0x6000014e80c0>{number = 1, Name = main} 2021-04-10 11:50:21.339184+0800 NSOperation01- Basic usage [4992:165002] 4----<NSThread: 0x6000014ad0c0>{number = 5, Name = (NULL)} 2021-04-10 11:50:21.339374+0800 NSOperation01- Basic usage [4992:164915] 2----<NSThread: 0x6000014e80c0>{number = 1, Name = main} 2021-04-10 11:50:21.339387+0800 NSOperation01- Basic usage [4992:165002] 5----<NSThread: 0x6000014ad0c0>{number = 5, Name = (NULL)} 2021-04-10 11:50:21.339614+0800 NSOperation01- Basic usage [4992:164915] 3----<NSThread: 0x6000014e80c0>{number = 1, Name = main} 2021-04-10 11:50:21.339653+0800 NSOperation01- Basic usage [4992:165002] 6----<NSThread: 0x6000014ad0c0>{number = 5, name = (null)}Copy the code

Now has three operation, six tasks, each operation there is a corresponding two tasks, you can see after the execution of task 1 open the child thread to perform a task 4 (task 4 is additional in the operation of 1), implements the multi-thread, the number of tasks in the same operation is greater than 1, task execution of concurrent execution is in operation, do not guarantee the order, The appended task may not be executed in the child thread, but may also be the main thread

Used in conjunction with NSOperationQueue

There are two types of NSOperationQueue

  • The home side column
    • Same as the main queue in GCD
    • [NSOperationQueue mainQueue];
  • The master queue
    • Very special: concurrent and serial capabilities (concurrent by default)
    • [[NSOperationQueue alloc]init];

Code sample

  1. NSInvocationOperation
-(void)invocationOperation{ //1. Create a task, Encapsulating task NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task1) object:nil]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //3. Add an operation to the queue [queue addOperation:op1]; // call [op1 start] automatically inside this method; }Copy the code
Result 2021-04-10 15:28:15.018135+0800 NSOperation02- Basic use of NSOperationQueue [7569:271371] -[ViewController task1]----<NSThread: 0x60000233c240>{number = 3, name = (null)}Copy the code

You can see that the child thread is started

  1. NSBlockOperation
-(void)blockOperationWithQueue{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"%s----%@",__func__,[NSThread currentThread]);
    }];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:op1];
}
Copy the code
2021-04-10 15:32:16.835307+0800 NSOperation02- Basic use of NSOperationQueue [76040:275382] -[ViewController blockOperationWithQueue]_block_invoke----<NSThread: 0x600000741f80>{number = 5, name = (null)}Copy the code

Is also implemented to open the child thread, if there are multiple operations, is executed concurrently

Another easy way to add a task to a queue is to create an operation and then add it to the queue, as follows

/ / easy way [queue addOperationWithBlock: ^ {NSLog (@ - % @ "% s", __func__, [NSThread currentThread]);}].Copy the code

Custom NSOperation

Benefits of customization

  • Helps code hide
  • Good for code reuse

Method of use

  1. Start with a custom subclass ZSOperation that inherits from NSOperation
  2. Normal operation
// ViewController.m -(void)customWithQueue{ //1. ZSOperation *op1 = [ZSOperation alloc]init]; ZSOperation *op2 = [[ZSOperation alloc]init]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //3. Add the operation to queue [queue addOperation:op1]; [queue addOperation:op2]; }Copy the code
  1. Override main in a subclass
-(void)main{NSLog(@"main----%@",[NSThread currentThread]); }Copy the code

Other methods of NSOperation

Set the maximum number of concurrent requests

As mentioned above, non-primary queues are concurrent by default, so how do we make them serial? The maximum number of concurrent maxConcurrentOperationCount them is required

MaxConcurrentOperationCount the default value is 1, the meaning of 1 in the computer is a maximum

Code sample

-(void)test{ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; / / set the maximum number of concurrent queue. MaxConcurrentOperationCount = 2; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1-----%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2-----%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3-----%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4-----%@",[NSThread currentThread]); }]; NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"5-----%@",[NSThread currentThread]); }]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; [queue addOperation:op4]; [queue addOperation:op5]; }Copy the code
2021-04-10 16:27:33.474160+0800 NSOperation03- Other Usage [855:317629] 2-----<NSThread: 0x600003340e00>{number = 6, Name = (null)} 2021-04-10 16:27:33.474167+0800 NSOperation03- other usage [855:317630] 1-----<NSThread: 0x60000335b480>{number = 5, Name = (null)} 2021-04-10 16:27:33.474397+0800 NSOperation03- other usage [855:317629] 3-----<NSThread: 0x600003340e00>{number = 6, Name = (null)} 2021-04-10 16:27:33.474444+0800 NSOperation03- other usage [855:317630] 4-----<NSThread: 0x60000335b480>{number = 5, Name = (null)} 2021-04-10 16:27:33.474552+0800 NSOperation03- other usage [855:317629] 5-----<NSThread: 0x600003340e00>{number = 6, name = (null)}Copy the code

The maximum number of concurrent operations controls not the number of threads, but the maximum number of concurrent operations in a queue

When hope is serial task will maxConcurrentOperationCount = 1 can, you can see below only opened up a thread, serial and task

2021-04-10 16:30:44.333695+0800 NSOperation03- Other Usage [8613:320761] 1-----<NSThread: 0x600003de1e80>{number = 7, Name = (null)} 2021-04-10 16:30:44.333940+0800 NSOperation03- other usage [8613:320761] 2-----<NSThread: 0x600003de1e80>{number = 7, Name = (null)} 2021-04-10 16:30:44.334065+0800 NSOperation03- other usage [8613:320761] 3-----<NSThread: 0x600003de1e80>{number = 7, Name = (null)} 2021-04-10 16:30:44.334181+0800 NSOperation03- other usage [8613:320761] 4-----<NSThread: 0x600003de1e80>{number = 7, Name = (null)} 2021-04-10 16:30:44.334308+0800 NSOperation03- other usage [8613:320761] 5-----<NSThread: 0x600003de1e80>{number = 7, name = (null)}Copy the code

Pause, continue, cancel

Operations in queues are also stateful

  • Waiting for the
  • perform
  • The end of the

Three types of events

  • Suspended:[self.queue setSuspended:YES];
    • It’s recoverable
    • Clicking pause does not pause immediately, but waits for the action in progress to complete
  • Continue to:[self.queue setSuspended:NO];
  • Cancellation:[self.queue cancelAllOperations];
    • unrecoverable
    • Clicking Cancel does not cancel immediately, but waits for the action in progress to complete
    • Internally, cancel is implemented for all operations in the queue

The three types of events described above are the same for the two types of operations that are native to the system, however, the situation is different for custom operations

//  ViewController.m
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 1;
ZSOperation *op1 = [[ZSOperation alloc]init];
[self.queue addOperation:op1];
Copy the code
// ZSOperation. M - (void)main{// for (NSInteger I = 0; i < 10000; i++) { NSLog(@"download1 --- %zd --- %@",i,[NSThread currentThread]); } NSLog(@"+++++++++++++++++++++++++++++++++++++++"); for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download2 --- %zd --- %@",i,[NSThread currentThread]); } NSLog(@"+++++++++++++++++++++++++++++++++++++++"); for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download3 --- %zd --- %@",i,[NSThread currentThread]); }}Copy the code

Clicking pause is useless in this case: if it’s a custom action class, we only have one action to add to the queue, so we still need to wait for the action to complete before we pause

  • If you want to cancel, add a judgment to the overriding main methodif (self.isCancelled) return;, the complete code is as follows
- (void)main{// Three time operation for (NSInteger I = 0; i < 10000; i++) { NSLog(@"download1 --- %zd --- %@",i,[NSThread currentThread]); } if (self.isCancelled) return; NSLog(@"+++++++++++++++++++++++++++++++++++++++"); for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download2 --- %zd --- %@",i,[NSThread currentThread]); } if (self.isCancelled) return; NSLog(@"+++++++++++++++++++++++++++++++++++++++"); for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download3 --- %zd --- %@",i,[NSThread currentThread]); } if (self.isCancelled) return; }Copy the code

Click Cancel while the time-consuming download1 operation is in progress, and the for loop will return and the operation will be canceled

  • If you want to stop immediately when you click Cancel, you can decideif (self.isCancelled) return;Inside the time-consuming operation, the code is as follows
for (NSInteger i = 0; i < 10000; i++) {
    if (self.isCancelled) return;
    NSLog(@"download1 --- %zd --- %@",i,[NSThread currentThread]);
}
Copy the code

However, it is not recommended to include this judgment in time-consuming operations, as it will affect performance (assuming a loop of 100,000 times, then 100,000 judgments should also be made….)

NSOperation Operation dependency and operation listening

Operation depends on

You can actually control the sequence of operations by adding Operation

-(void)dependency{ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1---%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2---%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3---%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4---%@",[NSThread currentThread]); }]; // Add operation dependency [op1 addDependency:op2]; [op3 addDependency:op4]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; [queue addOperation:op4]; }Copy the code
2021-04-10 21:07:43.007735+0800 NSOperation04- Operation dependencies and Listening [11737:466406] 4-- <NSThread: 0x600001ebf080>{number = 6, Name = (NULL)} 2021-04-10 21:07:43.007762+0800 NSOperation04- Operation dependencies and Monitoring [11737:466411] 2-- <NSThread: 0x600001ec0300>{number = 5, Name = (NULL)} 2021-04-10 21:07:43.007905+0800 NSOperation04- Operation dependencies and Listening [11737:466411] 1-- <NSThread: 0x600001ec0300>{number = 5, Name = (NULL)} 2021-04-10 21:07:43.007949+0800 NSOperation04- Operation dependencies and Listening [11737:466408] 3-- <NSThread: 0x600001ec5c40>{number = 4, name = (null)}Copy the code

You can see that operation 1 is executed after operation 2 is complete and operation 3 is executed after operation 4 is complete

Cross-queue dependencies can also be set, assuming a second queue is created and operation 4 is added to the queue

Operation monitoring

For example, our requirement is that we want some action to be notified when it’s finished, which is done by the completionBlock

-(void)dependency{ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1---%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2---%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3---%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4---%@",[NSThread currentThread]); }]; Op3.com pletionBlock = ^{NSLog(@" Operation complete!" ); }; // Add operation dependency [op1 addDependency:op2]; [op3 addDependency:op4]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; [queue addOperation:op4]; }Copy the code
2021-04-10 21:16:35.196159+0800 NSOperation04- Operation Dependencies and Listening [1196:477095] 4-- <NSThread: 0x600000f8b680>{number = 7, Name = (NULL)} 2021-04-10 21:16:35.196198+0800 NSOperation04- Dependency and Listener [1196:477092] 2-- <NSThread: 0x600000f88700>{number = 5, Name = (NULL)} 2021-04-10 21:16:35.196442+0800 NSOperation04- Dependency and Listener [1196:477095] 3-- <NSThread: 0x600000f8b680>{number = 7, Name = (NULL)} 2021-04-10 21:16:35.196568+0800 NSOperation04- Dependency and Listener [1196:477092] 1-- <NSThread: 0x600000F88700 >{Number = 5, Name = (NULL)} 2021-04-10 21:16:35.196645+0800 NSOperation04- Dependency and Listening [1196:477095] --<NSThread: 0x600000f8b680>{number = 7, name = (null)}Copy the code

You can see that it prints after operation 3 is performed (but not necessarily immediately, since it is executed concurrently) and not necessarily on the same thread

Thread communication

Again, we implement addOperationWithBlock as an append, but note that the queue for the operation should be the main queue, since updating the UI is done in the main thread

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSOperationQueue *queue = [[NSOperationQueue  alloc]init]; NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn%2F20170612%2Faeee-fyfzsyc249 6417. Jpg&refer = HTTP % % 2 f % 2 fn. 3 a sinaimg. Cn&app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? The SEC = 1620654759 & t = 95 a4ba561b2a 946b12992943bef34a71"]; NSData *imageData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage NSLog(@"download--------%@",[NSThread currentThread]); [[NSOperationQueue mainQueue] addOperationWithBlock:^{self.imageView.image = image; NSLog (@ "update UI -- -- -- -- -- -- - % @", [NSThread currentThread]);}];}]; [queue addOperation:download]; }Copy the code
2021-04-10 21:57:47.864482+0800 NSOperation05- Communication between threads [12567:509199] download--------<NSThread: 0x600002357780>{number = 5, Name = (NULL)} 2021-04-10 21:57:47.865169+0800 NSOperation05- Communication between threads [12567:508929] update UI--------<NSThread: 0x600002340240>{number = 1, name = main}Copy the code

Another example is merging images

-(void)combie{ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; __block UIImage *image1; __block UIImage *image2; NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{// download picture 1 NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn%2F20170612%2Faeee-fyfzsyc249 6417. Jpg&refer = HTTP % % 2 f % 2 fn. 3 a sinaimg. Cn&app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? The SEC = 1620654759 & t = 95 a4ba561b2a 946b12992943bef34a71"]; NSData *imageData = [NSData dataWithContentsOfURL:url]; image1 = [UIImage imageWithData:imageData]; NSLog(@"download1--------%@",[NSThread currentThread]); }]; NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{// download picture 2 NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn%2F20170612%2Faeee-fyfzsyc249 6417. Jpg&refer = HTTP % % 2 f % 2 fn. 3 a sinaimg. Cn&app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? The SEC = 1620654759 & t = 95 a4ba561b2a 946b12992943bef34a71"]; NSData *imageData = [NSData dataWithContentsOfURL:url]; image2 = [UIImage imageWithData:imageData]; NSLog(@"download2--------%@",[NSThread currentThread]); }]; NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{//1. Enable the context UIGraphicsBeginImageContext (CGSizeMake (800, 800)); / / 2. The drawing 1 [image1 drawInRect: CGRectMake (0, 0, 800, [image2 drawInRect:CGRectMake(0, 400, 800)]; 400)]; / / 4. Get the picture according to the context UIImage * image = UIGraphicsGetImageFromCurrentImageContext (); / / 5. Close the context UIGraphicsEndImageContext (); / / update the UI, [[NSOperationQueue mainQueue] addOperationWithBlock:^{self.imageView.image = image; NSLog (@ "update UI -- -- -- -- -- -- - % @", [NSThread currentThread]);}];}]; // Set dependency [combie addDependency:download1]; [combie addDependency:download2]; [queue addOperation:download1]; [queue addOperation:download2]; [queue addOperation:combie]; }Copy the code