多線程-NSOperation和NSOperationQueue


NSOperation

NSOperation類是用來封裝在單個任務相關的代碼和數據的抽象類。NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。
** 因為它是用來封裝任務的,大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列,
但是NSOperation本身又有執行多線程的能力跟GCD里的任務還是有區別的** 。

操作步驟也很好理解:

  • 將要執行的任務封裝到一個 NSOperation 對象中。
  • 將此任務添加到一個 NSOperationQueue 對象中。
    然后系統就會自動在執行任務。
創建任務

NSOperation 只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation 和 NSBlockOperation 。創建一個 Operation 后,需要調用 start 方法來啟動任務,它會默認在當前隊列同步執行。當然你也可以在中途取消一個任務,只需要調用其 cancel 方法即可。

 #.創建NSInvocationOperation對象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
  //開始執行
 [operation start];
 # 創建一個NSOperation不應該直接調用start方法(如果直接start則會在主線程中調用)而是應該放到NSOperationQueue中啟動。

 #.創建NSBlockOperation對象
 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
  }];
  //開始任務
  [operation start];

注意:之前說過這樣的任務,默認會在當前線程執行。但是 NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 添加多個執行 Block。這樣 Operation 中的任務 會并發執行,它會 在主線程和其它的多個線程 執行這些任務.。并且ddExecutionBlock 方法必須在 start() 方法之前執行,否則就會報錯。

# 創建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];
# 添加多個Block
  for (NSInteger i = 0; i < 5; i++) {
      [operation addExecutionBlock:^{
          NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
      }];
  }
  #開始任務,你會發現,會在多個線程中打印,其中包括主線程。
 [operation start];

自定義Operation

除了上面的兩種 Operation 以外,我們還可以自定義 Operation。自定義 Operation 需要繼承 NSOperation 類,并實現其 main() 方法,因為在調用 start() 方法的時候,內部會調用 main() 方法完成相關邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實現什么功能都可以寫在里面。除此之外,你還需要實現 cancel() 在內的各種方法。

創建隊列

我們可以調用一個 NSOperation 對象的 start() 方法來啟動這個任務,這個默認是 同步執行 的。就算是 addExecutionBlock 方法,也會在 當前線程和其他線程 中執行,也就是說還是會占用當前線程。如果你不想這個任務在主線程中執行(代碼默認情況下都在主線程中執行。)這是就要用到隊列 NSOperationQueue 。按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調用任務的 start() 方法。

  • 主隊列
    主隊列是串行隊列,添加到主隊列的任務都會一個接一個地排著隊在主線程處理。
    # 獲取到主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他創建的隊列
    主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。并且其他隊列的任務會在其他線程并行執行。請注意這里是并行執行。

    #1.創建一個其他隊列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    #2.創建NSBlockOperation對象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
     #.添加多個Block
    for (NSInteger i = 0; i < 5; i++) {
        [operation addExecutionBlock:^{
            NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
        }];
    }
     #.隊列添加任務
    [queue addOperation:operation];
    
NSOperationQueue的一些特殊使用
  • 設置最大并發數

我們將 NSOperationQueue 與 GCD的隊列 相比較就會發現,這里沒有串行隊列,那如果我想要10個任務在其他線程串行的執行的話,NSOperationQueue 有一個參數 maxConcurrentOperationCount 最大并發數,用來設置最多可以讓多少個任務同時執行。當你把它設置為 1 的時候,就相當于是串行隊列了

  • NSOperationQueue 添加一個任務到隊列
    - (void)addOperationWithBlock:(void (^)(void))block; 這樣就可以添加一個任務到隊列中了。

  • NSOperation 之間添加依賴

    NSOperation 有一個非常實用的功能,那就是對任務添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了:

    #1.任務一:下載圖片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #2.任務二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #3.任務三:上傳圖片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #4.設置依賴
    [operation2 addDependency:operation1];      //任務二依賴任務一
    [operation3 addDependency:operation2];      //任務三依賴任務二
    
    #5.創建隊列并加入任務
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
    

    注意
    A.不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
    B.可以使用 removeDependency 來解除依賴關系。
    C.可以在不同的隊列之間依賴,依賴是添加到任務身上的,和隊列沒關系。

  • 線程的掛起

    #pragma mark - 線程的掛起
    #暫停繼續(對隊列的暫停和繼續),掛起的是隊列,不會影響已經在執行的操作
    - (IBAction)pause:(UIButton *)sender {
    //判斷操作的數量,當前隊列里面是不是有操作?
    if (self.opQueue.operationCount == 0) {
      NSLog(@"當前隊列沒有操作");
      return;
    }
    self.opQueue.suspended = !self.opQueue.isSuspended;
    if (self.opQueue.suspended) {
      NSLog(@"暫停");
    }else{
      NSLog(@"繼續");
    }
    

    }

  • 取消隊列里的所有操作

    #pragma mark - 取消隊列里的所有操作
    - (IBAction)cancelAll:(UIButton *)sender {
    # 只能取消所有隊列的里面的操作,正在執行的無法取消
    # 取消操作并不會影響隊列的掛起狀態
    [self.opQueue cancelAllOperations];
    NSLog(@"取消隊列里所有的操作");
    //取消隊列的掛起狀態
    #(只要是取消了隊列的操作,我們就把隊列處于不掛起狀態,以便于后續的開始)
    self.opQueue.suspended = NO;
    }
    

其他方法

以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

#  NSOperation
BOOL executing; //判斷任務是否正在執行
BOOL finished; //判斷任務是否完成
#當一個 operation 被取消時,它的 completion block 仍然會執行,
#所以我們需要在真正執行代碼前檢查一下 isCancelled 方法的返回值。
- void (^completionBlock)(void); //用來設置完成后需要執行的操作
 
- (void)cancel; //取消任務
- (void)waitUntilFinished; //阻塞當前線程直到此任務執行完畢

#  NSOperationQueue
NSUInteger operationCount; //獲取隊列的任務數
- (void)cancelAllOperations; //取消隊列中所有的任務
#阻塞當前線程直到此隊列中的所有任務執行完畢
# **可以用來處理所有任務完成后的事件**
- (void)waitUntilAllOperationsAreFinished; 
[queue setSuspended:YES]; // 暫停queue
[queue setSuspended:NO]; // 繼續queue
NSOperation和GCD的區別和類似的地方
  • GCD是純C語言的API,NSOperationQueue是基于GCD的OC版本封裝
  • GCD只支持FIFO的隊列,NSOperationQueue可以很方便地調整執行順序、設置最大并發數量
  • NSOperationQueue可以在輕松在Operation間設置依賴關系,而GCD需要寫很多的代碼才能實現
  • NSOperationQueue支持KVO,可以監測operation是否正在執行(isExecuted)、是否結束(isFinished),是否取消(isCanceld)
  • GCD的執行速度比NSOperationQueue快
    **任務之間沒有什么依賴關系,而是需要更高的并發能力:GCD **
    任務之間有依賴、或者要監聽任務的執行情況、需要暫停、繼續任務:NSOperationQueue

小結

以上就是關于 NSOperation和NSOperationQueue 的主要知識了,后期如果有更多內容會同步更新。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容