多執行緒之NSOperation

iOS_時光荏苒發表於2019-06-04

NSOperation、NSOperationQueue 是蘋果提供給我們的一套多執行緒解決方案。實際上 NSOperation、NSOperationQueue 是基於 GCD 更高一層的封裝,完全物件導向

好處

  • 1、可新增完成的程式碼塊,在操作完成後執行
  • 2、新增操作之間的依賴關係,方便的控制執行順序
  • 3、設定操作執行的優先順序
  • 4、可以很方便的取消一個操作的執行
  • 5、使用 KVO 觀察對操作執行狀態的更改:isExecuteing、isFinished、isCancelled

既然是基於 GCD 的更高一層的封裝。那麼,GCD 中的一些概念同樣適用於 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有類似的任務(操作)和佇列(操作佇列)的概念

操作(Operation)

  • 1、執行操作的意思,換句話說就是你線上程中執行的那段程式碼
  • 2、在 GCD中是放在 block 中的。在 NSOperation 中,我們使用 NSOperation 子類 NSInvocationOperationNSBlockOperation,或者自定義子類來封裝操作

操作佇列(Operation Queues)

  • 1、這裡的佇列指操作佇列,即用來存放操作的佇列。不同於 GCD 中的排程佇列 FIFO(先進先出)的原則。NSOperationQueue 對於新增到佇列中的操作,首先進入準備就緒的狀態(就緒狀態取決於操作之間的依賴關係),然後進入就緒狀態的操作的開始執行順序(非結束執行順序)由操作之間相對的優先順序決定(優先順序是操作物件自身的屬性)。
  • 2、操作佇列通過設定最大併發運算元(maxConcurrentOperationCount)來控制併發、序列
  • 3、NSOperationQueue 為我們提供了兩種不同型別的佇列:主佇列和自定義佇列。主佇列執行在主執行緒之上,而自定義佇列在後臺執行

常用API

NSOperation常用屬性和方法

  • 1、開始取消操作

    • - (void)start:對於併發Operation需要重寫該方法,也可以不把operation加入到佇列中,手動觸發執行,與呼叫普通方法一樣
    • - (void)main:非併發Operation需要重寫該方法
    • - (void)cancel:可取消操作,實質是標記 isCancelled 狀態
  • 2、判斷操作狀態方法

    • - (BOOL)isFinished; 判斷操作是否已經結束
    • - (BOOL)isCancelled 判斷操作是否已經標記為取消
    • - (BOOL)isExecuting;判斷操作是否正在在執行
    • - (BOOL)isReady;判斷操作是否處於準備就緒狀態,這個值和操作的依賴關係相關。
  • 3、操作同步

    • - (void)waitUntilFinished;阻塞當前執行緒,直到該操作結束。可用於執行緒執行順序的同步
    • - (void)setCompletionBlock:(void (^)(void))block; 會在當前操作執行完畢時執行 completionBlock
    • - (void)addDependency:(NSOperation *)op; 新增依賴,使當前操作依賴於操作 op 的完成
    • - (void)removeDependency:(NSOperation *)op; 移除依賴,取消當前操作對操作 op 的依賴。
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當前操作開始執行之前完成執行的所有操作物件陣列。

NSOperationQueue 常用屬性和方法

  • 1、取消/暫停/恢復操作

    • - (void)cancelAllOperations; 可以取消佇列的所有操作
    • - (BOOL)isSuspended; 判斷佇列是否處於暫停狀態。 YES 為暫停狀態,NO 為恢復狀態
    • - (void)setSuspended:(BOOL)b; 可設定操作的暫停和恢復,YES 代表暫停佇列,NO 代表恢復佇列
  • 2、操作同步

    • - (void)waitUntilAllOperationsAreFinished; 阻塞當前執行緒,直到佇列中的操作全部執行完畢。
  • 3、新增/獲取操作

    • - (void)addOperationWithBlock:(void (^)(void))block; 向佇列中新增一個 NSBlockOperation 型別操作物件
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向佇列中新增運算元組,wait 標誌是否阻塞當前執行緒直到所有操作結束
    • - (NSArray *)operations; 當前在佇列中的運算元組(某個操作執行結束後會自動從這個陣列清除)
    • - (NSUInteger)operationCount; 當前佇列中的運算元
  • 4、獲取佇列

    • + (id)currentQueue; 獲取當前佇列,如果當前執行緒不是在 NSOperationQueue 上執行則返回 nil。
    • + (id)mainQueue; 獲取主佇列。

簡單使用

NSOperation 需要配合 NSOperationQueue 來實現多執行緒。因為預設情況下,NSOperation 單獨使用時系統同步執行操作,配合 NSOperationQueue 我們能更好的實現非同步執行

實現步驟

  • 1、建立操作:先將需要執行的操作封裝到一個 NSOperation 物件中
  • 2、建立佇列:建立 NSOperationQueue 物件
  • 3、將操作加入到佇列中:將 NSOperation 物件新增到 NSOperationQueue 物件中

NSOperation 是個抽象類,不能用來封裝操作。我們只有使用它的子類來封裝操作

  • 1、使用子類 NSInvocationOperation
  • 2、使用子類 NSBlockOperation
  • 3、自定義繼承自 NSOperation 的子類,通過實現內部相應的方法來封裝操作。

使用子類 NSInvocationOperation

在主執行緒中操作

- (void)Operation1{
//1、建立NSInvocationOperation物件
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
//2、開始呼叫
[op start];
}

- (void)test{
for (NSInteger i = 0; i < 2; i++) {
NSLog(@"當前執行緒:%@",[NSThread currentThread]);
}
}

複製程式碼

多執行緒之NSOperation

如果在其他執行緒中操作

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(Operation1) object:@"Hello, World"];
//設定執行緒的名字,方便檢視
[thread setName:@"firstThread"];
//啟動執行緒
[thread start];

複製程式碼

多執行緒之NSOperation

總結:在沒有使用 NSOperationQueue、在主執行緒中單獨使用使用子類 NSInvocationOperation 執行一個操作的情況下,操作是在當前執行緒執行的,並沒有開啟新執行緒

使用子類 NSBlockOperation

在主執行緒中

- (void)Operation2{
//1、使用NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當前執行緒:%@",[NSThread currentThread]);
}];
//2、開始呼叫
[op start];
}

複製程式碼

多執行緒之NSOperation

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(Operation2) object:@"Hello, World"];
//設定執行緒的名字,方便檢視
[thread setName:@"firstThread"];
//啟動執行緒
[thread start];

複製程式碼

多執行緒之NSOperation

總結:在沒有使用 NSOperationQueue、在主執行緒中單獨使用使用子類 NSBlockOperation 執行一個操作的情況下,操作是在當前執行緒執行的,並沒有開啟新執行緒

addExecutionBlock

NSBlockOperation 還提供了一個方法 addExecutionBlock:,通過 addExecutionBlock: 就可以為 NSBlockOperation 新增額外的操作。這些操作(包括 blockOperationWithBlock 中的操作)可以在不同的執行緒中同時(併發)執行。只有當所有相關的操作已經完成執行時,才視為完成。每新增一個addExecutionBlock:就是開啟一個非同步併發執行事件

//1、使用NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當前執行緒:%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"當前執行緒2:%@", [NSThread currentThread]); // 列印當前執行緒
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"當前執行緒3:%@", [NSThread currentThread]); // 列印當前執行緒
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"當前執行緒4:%@", [NSThread currentThread]); // 列印當前執行緒
}
}];

//2、開始呼叫
[op start];

複製程式碼

多執行緒之NSOperation

自定義繼承自 NSOperation 的子類

如果使用子類NSInvocationOperationNSBlockOperation不能滿足日常需求,我們可以使用自定義繼承自 NSOperation 的子類。可以通過重寫 main或者 start方法 來定義自己的 NSOperation 物件。重寫main方法比較簡單,我們不需要管理操作的狀態屬性isExecuting 和 isFinished。當 main 執行完返回的時候,這個操作就結束了

@interface JHOperation : NSOperation

@end
#import "JHOperation.h"

@implementation JHOperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {

NSLog(@"當前執行緒:%@", [NSThread currentThread]);
}
}
}

@end

複製程式碼

呼叫

- (void)Operation3{
// 1.建立 JHOperation 物件
JHOperation *op = [[JHOperation alloc] init];
// 2.呼叫 start 方法開始執行操作
[op start];
}

複製程式碼

NSOperationQueue 建立佇列

NSOperationQueue 一共有兩種佇列:主佇列、自定義佇列。其中自定義佇列同時包含了序列、併發功能

主佇列 凡是新增到主佇列中的操作,都會放到主執行緒中執行(注:不包括操作使用addExecutionBlock:新增的額外操作)

// 主佇列獲取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

複製程式碼

自定義佇列 新增到這種佇列中的操作,就會自動放到子執行緒中執行。同時包含了:序列、併發功能。

// 自定義佇列建立方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

複製程式碼

將操作加入到佇列中

NSOperation 需要配合 NSOperationQueue 來實現多執行緒,總共有兩種方法:

  • 1、- (void)addOperation:(NSOperation *)op; 需要先建立操作,再將建立好的操作加入到建立好的佇列中去
  • 2、- (void)addOperationWithBlock:(void (^)(void))block; 無需先建立操作,在 block 中新增操作,直接將包含操作的 block 加入到佇列中。

addOperation

- (void)Operation4{
//1、建立佇列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

//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]);
}];

//3、新增操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

複製程式碼

多執行緒之NSOperation

addOperationWithBlock

無需先建立操作,在 block 中新增操作,直接將包含操作的 block 加入到佇列中。

- (void)Operation5{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒3:%@",[NSThread currentThread]);
}];
}

複製程式碼

最大併發運算元量 maxConcurrentOperationCount

maxConcurrentOperationCount最大併發運算元量

  • maxConcurrentOperationCount 預設情況下為-1,表示不進行限制,可進行併發執行。
  • maxConcurrentOperationCount 為1時,佇列為序列佇列。只能序列執行。
  • maxConcurrentOperationCount 大於1時,佇列為併發佇列。操作併發執行,當然這個值不應超過系統限制,即使自己設定一個很大的值,系統也會自動調整為 min{自己設定的值,系統設定的預設最大值}
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1; // 序列佇列
queue.maxConcurrentOperationCount = 2; // 併發佇列,一次只能執行兩個併發佇列
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"當前執行緒3:%@",[NSThread currentThread]);
}];

複製程式碼

NSOperation 操作依賴

NSOperation、NSOperationQueue 最吸引人的地方是它能新增操作之間的依賴關係。通過操作依賴,我們可以很方便的控制操作之間的執行先後順序

  • - (void)addDependency:(NSOperation *)op;新增依賴,使當前操作依賴於操作 op 的完成。
  • - (void)removeDependency:(NSOperation *)op;移除依賴,取消當前操作對操作 op 的依賴。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當前操作開始執行之前完成執行的所有操作物件陣列
- (void)Operation6{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2、建立操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"當前執行緒1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"當前執行緒2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當前執行緒3:%@",[NSThread currentThread]);
}];

//3、新增依賴
[op3 addDependency:op1];
[op3 addDependency:op2];
//4、新增操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];

}

複製程式碼

上面op3依賴op1&op2執行完成才能執行

多執行緒之NSOperation

NSOperation 優先順序

NSOperation 提供了queuePriority(優先順序)屬性,queuePriority屬性適用於同一操作佇列中的操作,不適用於不同操作佇列中的操作。預設情況下,所有新建立的操作物件優先順序都是NSOperationQueuePriorityNormal。但是我們可以通過setQueuePriority:方法來改變當前操作在同一佇列中的執行優先順序

// 優先順序的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};

複製程式碼

對於新增到佇列中的操作,首先進入準備就緒的狀態(就緒狀態取決於操作之間的依賴關係),然後進入就緒狀態的操作的開始執行順序由操作之間相對的優先順序決定(優先順序是操作物件自身的屬性)

說明:優先順序高的任務,呼叫的機率會更大。

結語

以上就是這篇文章的全部內容了,希望本文的內容對大傢俱有一定的參考學習價值,最後推薦個 iOS高階開發交流圈:624212887,裡面都是iOS開發,全棧發展,歡迎入駐,共同進步!謝謝大家的支援

相關文章