天天看點

iOS開發之多線程(4)—— Operation文集版本簡介基本用法

文集

iOS開發之多線程(1)—— 概述

iOS開發之多線程(2)—— Thread

iOS開發之多線程(3)—— GCD

iOS開發之多線程(4)—— Operation

iOS開發之多線程(5)—— Pthreads

iOS開發之多線程(6)—— 線程安全與各種鎖

目錄

  • 文集
  • 版本
  • 簡介
      • 操作
      • 同步 & 異步
      • 隊列
      • 串行 & 并行
  • 基本用法
      • 1. 不使用隊列
      • 2. 使用隊列
          • 2.1 四種添加操作(任務)方法
          • 2.2 其他屬性方法
          • 2.3 線程間通信

版本

Xcode 11.5

Swift 5.2.2

簡介

我們先來讨論相關理論知識點, 代碼部分統一放到後文.

Operation基于GCD封裝, 完全面向對象. 對應于GCD, Operation也有任務和隊列的概念, 隻不過在Operation中任務(block)被稱為操作(operation).

Operation支援以下關鍵功能:

  1. 操作之間添加依賴
  2. 使用KVO監聽操作的執行狀态
  3. 對操作進行優先級排序
  4. 取消操作

操作

  • 在GCD中, 任務代碼隻能寫在block中, 并且需要放到隊列(dispatch queue)中去執行.
  • 而在Operation中, 操作對象可以單獨執行, 也可以添加到隊列 (operation queue)去執行.

Operation是一個抽象類, 代表一個任務. 通常我們使用它的子類NSInvocationOperation或NSBlockOperation來編寫任務代碼. 當然也可以直接使用Operation, 不過需要重寫main方法, 在main裡面編寫任務代碼.

3種建立方式:

  1. NSInvocationOperation (swift不支援): 此類調用選擇器方法(selector), 在方法裡面編寫任務代碼.
  2. NSBlockOperation (swift對應BlockOperation): 此類采用block方式, 在block中編寫任務代碼.
  3. NSOperation (swift對應Operation): 需要重寫main方法, 在main裡面編寫任務代碼.

2種執行方式:

  1. 不添加到隊列, 手動調用operation的start方法.
  2. 添加到隊列, 系統自動調用start方法.

具體如何使用請看下小節.

同步 & 異步

在GCD中, 同步和異步分别對應dispatch_sync和dispatch_async方法.

在Operation中, 沒有這種方法.

蘋果文檔

如果計劃手動執行操作對象,而不是将其添加到隊列中,則可以将操作設計為以同步或異步方式執行。預設情況下,操作對象是同步的。當start()直接從代碼中調用同步操作的方法時,該操作将在目前線程中立即執行。

預設情況下,操作對象以同步方式執行-也就是說,它們在調用其start方法的線程中執行其任務。

為了獲得最佳性能,您應該将操作設計為盡可能異步,使應用程式在執行操作時可以自由地做其他工作。

  • 如果不使用隊列, operation預設以同步方式執行. 但我們有辦法使之異步執行: 建立一個新線程, 然後在新線程裡面調用start方法.
  • 如果使用隊列, 系統預設以異步方式執行. 但我們可以使用waitUntilAllOperationsAreFinished (operation queue方法)進行等待, 以確定操作完成後才繼續往下執行.
注意: 與GCD的同步執行不同, 這裡雖然設定了等待, 但是一般不在目前線程執行, 而是新開一個線程來執行, 但會保證隊列裡的所有任務完成後才繼續往下執行.

隊列

隊列 (Operation Queue)有兩種: 主隊列和非主隊列 (自定義隊列).

  • 主隊列通過mainQueue獲得, 主隊列裡的任務都是放到主線程執行 (不包括使用addExecutionBlock:添加的額外操作, 因其可能在其他線程執行).
  • 非主隊列 (自定義隊列) 即一般 alloc init 出來的隊列, 預設在子線程中異步執行. 通多設定最大并發數(maxConcurrentOperationCount)來控制隊列是串行還是并發.

添加操作(任務)到隊列有四種方式:

  1. addOperation:

    添加一個現有的Operation (或者其子類).

  2. addOperations:waitUntilFinished:

    可添加多個現有的Operation (或者其子類), 可設定等待所有操作完成後方可繼續往下執行.

  3. addOperationWithBlock:

    直接添加一個block

  4. addBarrierBlock:

    添加栅欄, 順帶一個任務. 等栅欄前的所有任務都執行完, 再執行本栅欄的任務, 起到隔離同步等待的目的.

串行 & 并行

主隊列是串行隊列. 自定義隊列預設是并發隊列, 但可通多設定最大并發數(maxConcurrentOperationCount)來控制隊列是串行還是并發.

maxConcurrentOperationCount

-1, 預設值, 并發隊列;

=0, 不執行任何操作;

=1, 串行隊列;

<0, 除-1預設值外, 其他負值均報錯;

>1, 并發隊列, 如果數值過大, 最終并發數由系統決定.

基本用法

1. 不使用隊列

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
           
在自定義Operation中, 調用start方法後, 系統會執行多項安全檢查, 最終會調用main方法.

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. 使用隊列

2.1 四種添加操作(任務)方法

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 其他屬性方法

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
           

我們看到:

由于是串行隊列, 沒有添加依賴和設定優先級, 是以會按照1~4順序執行;

又因為設定了等待(waitUntilFinished:YES), 是以end最後才列印;

開不開新線程, 由系統決定.

添加依賴

設定優先級

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

取消隊列裡所有任務

2.3 線程間通信
- (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

繼續閱讀