Multithreading of iOS development (4)-Operation

Collected Works

Multithreading for iOS development (1)
-Overview of multithreading for iOS development (2)-Multithreading for Thread
iOS development (3)-Multithreading for GCD
iOS development (4)
-Operation Multithreading for iOS development ( 5)-Multithreading of Pthreads
iOS development (6)-Thread safety and various locks

version

Xcode 11.5
Swift 5.2.2

Introduction

Let's discuss the relevant theoretical knowledge points first, and the code part will be unified later.

Operation is based on GCD encapsulation and is fully object-oriented. Corresponding to GCD, Operation also has the concept of tasks and queues, but in Operation, the task (block) is called the operation (operation).

Operation supports the following key functions:

  1. Add dependencies between operations
  2. Use KVO to monitor the execution status of the operation
  3. Prioritize operations
  4. Cancel operation

operating

  • In GCD, task code can only be written in a block, and it needs to be placed in a dispatch queue for execution.
  • In Operation, the operation object can be executed separately or added to the operation queue for execution.

Operation is an abstract class that represents a task. Usually we use its subclasses NSInvocationOperation or NSBlockOperation to write task code. Of course, you can also use Operation directly, but you need to rewrite the main method to write task code in main.

3 ways to create :

  1. NSInvocationOperation (swift not supported): This type of call selector method (selector), write task code in the method.
  2. NSBlockOperation (swift corresponds to BlockOperation): This type uses the block method to write task code in the block.
  3. NSOperation (swift corresponding to Operation): You need to rewrite the main method and write task code in main.

2 ways of execution :

  1. Do not add to the queue, manually call the start method of operation.
  2. Add to the queue, the system automatically calls the start method.

Please see the next section for details on how to use it.

Synchronous Asynchronous

In GCD, synchronous and asynchronous correspond to dispatch_sync and dispatch_async methods respectively.
In Operation, there is no such method.

Apple document
If you plan to manually execute the operation object instead of adding it to the queue, you can design the operation to be executed synchronously or asynchronously. By default, the operation object is synchronized. When start() calls the method of synchronization operation directly from the code, the operation will be executed immediately in the current thread.
By default, operation objects are executed synchronously-that is, they perform their tasks in the thread that called their start method.
For the best performance, you should design the operation to be as asynchronous as possible so that the application is free to do other work while performing the operation.

  • If you don't use the queue, the operation is executed synchronously by default. But we have a way to make it execute asynchronously: create a new thread, and then call the start method in the new thread.
  • If you use a queue, the system executes asynchronously by default. But we can use waitUntilAllOperationsAreFinished (operation queue method) to wait to ensure that the operation is completed before continuing to execute.

Note: Different from the synchronous execution of GCD, although the wait is set here, it is generally not executed in the current thread, but a new thread is opened for execution, but it will continue to execute after all tasks in the queue are completed.

queue

There are two types of queues (Operation Queue): main queue and non-main queue (custom queue).

  • The main queue is obtained through mainQueue, and the tasks in the main queue are all executed on the main thread (not including the use of addExecutionBlock: additional operations added, because they may be executed in other threads).
  • The non-main queue (custom queue) is the queue from the general alloc init, which is executed asynchronously in the child thread by default. Set the maximum concurrency count (maxConcurrentOperationCount) to control whether the queue is serial or concurrent.

There are four ways to add operations (tasks) to the queue:

  1. addOperation:
    Add an existing Operation (or its subclass).
  2. addOperations:waitUntilFinished: You
    can add multiple existing Operations (or its subclasses), and you can set to wait for all operations to complete before continuing to execute.
  3. addOperationWithBlock:
    directly add a block
  4. addBarrierBlock:
    Add a fence, along with a task. After all the tasks in front of the fence have been executed, then execute the task of this fence to isolate and synchronize the waiting.

Serial & Parallel

The main queue is a serial queue. The custom queue is a concurrent queue by default, but you can set the maximum concurrency count (maxConcurrentOperationCount) to control whether the queue is serial or concurrent.

maxConcurrentOperationCount
-1, default value, concurrent queue;
=0, do not perform any operation;
=1, serial queue;
<0, except for the default value of -1, all other negative values ​​will report an error;
>1, concurrent queue, if the value is too high Large, the final concurrency number is determined by the system.

Basic usage

1. Do not use queues

OC

#pragma mark - NSInvocationOperation (调用selector方法)

// 使用 NSInvocationOperation
- (void)useInvocationOperation {
    
    
    
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doInvocationOperation) object:nil];
    [operation start];
}

// 任务
- (void)doInvocationOperation {
    
    
    
    NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
}

//---------------------------------------------------------------------------------------------------
//log:
//-[ViewController doInvocationOperation], thread:<NSThread: 0x6000017ec2c0>{number = 1, name = main}


#pragma mark - NSBlockOperation (使用block)

// 使用 NSBlockOperation
- (void)useBlockOperation {
    
    
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    
    
        
        NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
    }];
    
    [operation start];
}

//---------------------------------------------------------------------------------------------------
//log:
//-[ViewController useBlockOperation]_block_invoke, thread:<NSThread: 0x600000760100>{number = 1, name = main}


#pragma mark - 自定义Operation (重写main)

- (void)useCustomOperation {
    
    
    
    MyOperation *operation = [[MyOperation alloc] init];
    [operation start];
}

//---------------------------------------------------------------------------------------------------
//log:
//-[MyOperation main], thread:<NSThread: 0x600000618280>{number = 1, name = main}
MyOperation.m

@implementation MyOperation

- (void)main {
    
    
    
    NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
}

@end

In a custom operation, after calling the start method, the system will perform multiple security checks, and finally the main method will be called.

Swift

//MARK: - 使用 BlockOperation (block)
@objc func useBlockOperation() {
    
    
    
    let operation = BlockOperation.init {
    
    
        print("\(#function), thread:\(Thread.current)")
    }
    operation.start()
}
    
//---------------------------------------------------------------------------------------------------
//log:
//useBlockOperation(), thread:<NSThread: 0x600003f78000>{number = 1, name = main}
    

//MARK: - 使用 自定义Operation (重写main)
@objc func useCustomOperation() {
    
    
    
    let operation = CustomOperation.init()
    operation.start()
}
    
//---------------------------------------------------------------------------------------------------
//log:
//main(), thread:<NSThread: 0x600003ec8cc0>{number = 1, name = main}
class CustomOperation: Operation {
    
    
    
    override func main() {
    
    
        print("\(#function), thread:\(Thread.current)")
    }
}

2. Use queues

2.1 Four methods of adding operations (tasks)

OC

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

    // 1 addOperation:
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op1 thread:%@", [NSThread currentThread]);
    }] ;
    [operationQueue addOperation:op1];

    // 2 addOperations:waitUntilFinished:
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op2 thread:%@", [NSThread currentThread]);
    }] ;
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op3 thread:%@", [NSThread currentThread]);
    }] ;
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op4 thread:%@", [NSThread currentThread]);
    }] ;
    [operationQueue addOperations:@[op2, op3, op4] waitUntilFinished:YES];
    NSLog(@"queue finished");

    // 3 addOperationWithBlock:
    [operationQueue addOperationWithBlock:^{
    
    
        NSLog(@"block thread:%@", [NSThread currentThread]);
    }];

    // 4 addBarrierBlock:
    [operationQueue addBarrierBlock:^{
    
    
        NSLog(@"barrier thread:%@", [NSThread currentThread]);
    }];

//---------------------------------------------------------------------------------------------------
//log:
//op2 thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}
//op4 thread:<NSThread: 0x600002eaf180>{number = 4, name = (null)}
//op1 thread:<NSThread: 0x600002ed77c0>{number = 5, name = (null)}
//op3 thread:<NSThread: 0x600002eaf140>{number = 3, name = (null)}
//queue finished
//block thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}

Swift

    let operationQueue = OperationQueue.init()
        
    // 1 addOperation(_ op: Operation)
    let op1 = BlockOperation.init {
    
    
        print("op1, thread:\(Thread.current)")
    }
    operationQueue.addOperation(op1)
    
    
    // 2 addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
    let op2 = BlockOperation.init {
    
    
        print("op2, thread:\(Thread.current)")
    }
    let op3 = BlockOperation.init {
    
    
        print("op3, thread:\(Thread.current)")
    }
    let op4 = BlockOperation.init {
    
    
        print("op4, thread:\(Thread.current)")
    }
    operationQueue.addOperations([op2, op3, op4], waitUntilFinished: true)
    
    
    // 3 addOperation(_ block: @escaping () -> Void)
    operationQueue.addOperation {
    
    
        print("block, thread:\(Thread.current)")
    }
    

    // 4 addBarrierBlock(_ barrier: @escaping () -> Void)
    operationQueue.addBarrierBlock {
    
    
        print("barrier, thread:\(Thread.current)")
    }
        
//---------------------------------------------------------------------------------------------------
log:
op3, thread:<NSThread: 0x600002399200>{
    
    number = 4, name = (null)}
op1, thread:<NSThread: 0x6000023c34c0>{
    
    number = 5, name = (null)}
op2, thread:<NSThread: 0x600002398f80>{
    
    number = 6, name = (null)}
op4, thread:<NSThread: 0x6000023d42c0>{
    
    number = 7, name = (null)}
block, thread:<NSThread: 0x600002398f80>{
    
    number = 6, name = (null)}
barrier, thread:<NSThread: 0x600002398f80>{
    
    number = 6, name = (null)}
2.2 Other attribute methods

maxConcurrentOperationCount

- (void)setOperationCount {
    
    
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSLog(@"start");
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op1 thread:%@", [NSThread currentThread]);
    }] ;
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op2 thread:%@", [NSThread currentThread]);
    }] ;
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op3 thread:%@", [NSThread currentThread]);
    }] ;
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    
    
        NSLog(@"op4 thread:%@", [NSThread currentThread]);
    }] ;
    
    /**
     -1, 默认值, 并发队列;
     =0, 不执行任何操作;
     =1, 串行队列;
     <0, 除-1默认值外, 其他负值均报错;
     >1, 并发队列, 如果数值过大, 最终并发数由系统决定.
     */
    operationQueue.maxConcurrentOperationCount = 1;
    [operationQueue addOperations:@[op1, op2, op3, op4] waitUntilFinished:YES];
    
    NSLog(@"end");
}

//---------------------------------------------------------------------------------------------------
log:
start
op1 thread:<NSThread: 0x600000b181c0>{
    
    number = 5, name = (null)}
op2 thread:<NSThread: 0x600000b75f80>{
    
    number = 6, name = (null)}
op3 thread:<NSThread: 0x600000b75f80>{
    
    number = 6, name = (null)}
op4 thread:<NSThread: 0x600000b75f80>{
    
    number = 6, name = (null)}
end

We see:
because it is a serial queue, there is no dependency and priority setting, so it will be executed in the order of 1~4;
and because the wait (waitUntilFinished:YES) is set, the end is printed at the end;
a new thread cannot be opened, Determined by the system.

Add dependency

addDependency:

Set priority

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    
    
	NSOperationQueuePriorityVeryLow = -8L,
	NSOperationQueuePriorityLow = -4L,
	NSOperationQueuePriorityNormal = 0,
	NSOperationQueuePriorityHigh = 4,
	NSOperationQueuePriorityVeryHigh = 8
};

Cancel all tasks in the queue

- (void)cancelAllOperations;
2.3 Communication between threads
- (void)callMainQueue {
    
    
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    [operationQueue addOperationWithBlock:^{
    
    
        
        NSLog(@"block, thread:%@", [NSThread currentThread]);
        
        // call main queue
        [NSOperationQueue.mainQueue addOperationWithBlock:^{
    
    
            
            NSLog(@"main, thread:%@", [NSThread currentThread]);
        }];
    }];
}

//---------------------------------------------------------------------------------------------------
log:
block, thread:<NSThread: 0x600003ee0580>{
    
    number = 6, name = (null)}
main, thread:<NSThread: 0x600003ea0240>{
    
    number = 1, name = main}

demo
https://github.com/LittleLittleKang/KKThreadsDemo

Guess you like

Origin blog.csdn.net/u012078168/article/details/107232203