多線程編程(三)NSOperationQueue

本文我們來介紹一下多線程編程工具中的NSOperationQueue。


1. NSOperationQueue簡介

配合使用NSOperation和NSOperationQueue也可以實現多線程編程。使用NSOperationQueue方式進行多線程編程,不能夠像NSThread一樣直接創建線程,也不需要管理,但是可以間接的干預線程,這也是該方式的優點。NSOperationQueue同時引入了Queue(隊列)的概念,瞭解NSOperationQueue使用,首先要了解NSOperation和NSOperationQueue的關係。


2. NSOperation的使用

2.1 NSOperation與NSOperationQueue的關係

NSOperation類是一個抽象類,用來封裝單任務的代碼和數據。可以參考其英文解釋:

   The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task.你使用的NSOperation類是一個抽象類來封裝與單個任務相關的代碼和數據

所以,我們不能直接使用該類,而是使用系統定義的子類來完成實際的任務。iOS 提供了兩種默認實現:NSInvocationOperation 和 NSBlockOperation,當然也可以自定義 NSOperation。

需要注意的是,使用NSOperation的子類對象只能執行一次任務,而且不能再次執行他,可以將他添加到一個操作隊列中執行操作,這個操作隊列我們可以使用NSOperationQueue來實現。

2.2 創建NSOperation的創建方式

   NSOperation是個抽象類,並不具備封裝操作的能力,必須使⽤它的子類
   使用NSOperation⼦類的方式有3種:
 (1)NSInvocationOperation
 (2)NSBlockOperation
 (3)自定義子類繼承NSOperation,實現內部相應的⽅法。

NSBlockOperation可以通過addExecutionBlock添加新的任務,只要NSBlockOperation封裝的操作數大於1,就會異步執行操作。

NSOperation也是設計用來擴展的,只需繼承重寫 NSOperation的一個方法main。然後把NSOperation子類的對象放入NSOperationQueue隊列中,該隊列就會啓動並開始處理它。

   注意,需要強調的是,NSOperation默認是不執行的,需要調用start方法。同時,操作對象默認在主線程中執行,只有添加到隊列中才會開啓新的線程。即默認情況下,如果操作沒有放到隊列中queue中,都是同步執行。只有將NSOperation放到一個NSOperationQueue中,纔會異步執行操作

3. NSOperationQueue的使用

NSOperationQueue的作⽤:NSOperation可以調⽤start⽅法來執⾏任務,但默認是同步執行的。如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作。

NSOperation和NSOperationQueue實現多線程的具體步驟:
(1)先將需要執行的操作封裝到一個NSOperation對象中
(2)然後將NSOperation對象添加到NSOperationQueue中
(3)系統會⾃動將NSOperationQueue中的NSOperation取來
(4)將取出的NSOperation封裝的操作放到⼀條新線程中執⾏

注意:一個NSOperationQueue對象並非一個線程,而是線程管理器,可以幫我們自動創建新的線程。取決於隊列的最大並行數。NSOperation對象添加到隊列中,默認就成爲了異步執行(非當前線程)。

- 創建一個操作隊列

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

- 添加一個operation

      [queue addOperation:operation];

- 添加一組operation

      [queue addOperations:operations waitUntilFinished:NO];

- 添加一個block形式/隱式的operation

     [queue addOperationWithBlock:^() {
         NSLog(@"執行一個新的操作,線程:%@", [NSThread currentThread]);
     }];

NSOperation添加到queue之後,通常短時間內就會得到運行。但是如果存在依賴,或者整個queue被暫停等原因,也可能需要等待。

   注意:NSOperation添加到queue之後,絕對不要再修改NSOperation對象的狀態。因爲NSOperation對象可能會在任何時候運行,因此改變NSOperation對象的依賴或數據會產生不利的影響。你只能查看NSOperation對象的狀態,比如是否正在運行、等待運行、已經完成等。

3. NSOperation的運行順序

在執行NSOperationqueue中的任務時,系統自動將NSOperationqueue中的NSOperation對象取出,將其封裝的操作放到一條新的線程中執行。如果四個任務,開啓了四條線程。通過查看任務執行的時間可以看出,這些任務是並行執行的。

隊列的取出是有順序的,與打印結果並不矛盾。這就好比,選手A,B,C雖然起跑的順序是先A,後B,然後C,但是到達終點的順序卻不一定是A,B在前,C在後。
對於添加到queue中的operations,它們的執行順序取決於2點:

  • 首先看看NSOperation是否已經準備好:是否準備好由對象的依賴關係確定
  • 然後再根據所有NSOperation的相對優先級來確定。

3.1 NSOperation添加依賴關係

依賴關係會影響到NSOperation對象在queue中的執行順序,當某個NSOperation對象依賴於其它NSOperation對象的完成時,就可以通過addDependency方法添加一個或者多個依賴的對象:

[operation2 addDependency:operation1];

只有所有依賴的對象都已經完成操作,當前NSOperation對象纔會開始執行操作。另外,通過removeDependency方法來刪除依賴對象。

[operation2 removeDependency:operation1];

添加依賴成功的前提,必須將Operation添加到Queue中,但是要在將Operation添加到Queue中之前添加依賴。依賴關係不侷限於相同queue中的NSOperation對象。NSOperation對象會管理自己的依賴,,因此完全可以在不同的queue之間的NSOperation對象創建依賴關係。

注意:唯一的限制是不能創建環形依賴,比如A依賴B,B依賴A,這是錯誤的

3.2 修改Operations的優先級

優先級等級則是operation對象本身的一個屬性。默認所有operation都擁有“普通”優先級,不過可以通過setQueuePriority:方法來提升或降低operation對象的優先級。優先級只能應用於相同queue中的operations。如果應用有多個operation queue,每個queue的優先級等級是互相獨立的。因此不同queue中的低優先級操作仍然可能

優先級的取值

  • NSOperationQueuePriorityVeryLow = -8L,

  • NSOperationQueuePriorityLow = -4L,

  • NSOperationQueuePriorityNormal = 0,
  • NSOperationQueuePriorityHigh = 4,

  • NSOperationQueuePriorityVeryHigh = 8

    說明:優先級高的任務,調用的機率會更大。
    注意:優先級不能替代依賴關係,優先級只是對已經準備好的 operations 確定執行順序。先滿足依賴關係,然後再根據優先級從所有準備好的操作中選擇優先級最高的那個執行。

4.4 操作的監聽

如果需要設置A任務的執行晚於B任務,可以直接跟在任務後面編寫需要完成的操作,如這裏在下載圖片後,緊跟着下載第二張圖片。但是這種寫法有的時候把兩個不相關的操作寫到了一個代碼塊中,代碼的可閱讀性不強。

通過下面兩個方法,可以監聽一個操作的執行完畢,在添加其他任務。

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

示例代碼:

//創建對象,封裝操作

NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
        NSLog(@"-operation-下載圖片-%@",[NSThread currentThread]);
    }
}];
//監聽操作的執行完畢
operation.completionBlock=^{
   //.....下載圖片後繼續進行的操作
    NSLog(@"--接着下載第二張圖片--");
};
//創建隊列
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
//把任務添加到隊列中(自動執行,自動開線程)
[queue addOperation:operation];

4. NSOperationQueue的相關操作

4.1 併發數

併發數:同時執⾏行的任務數.比如,同時開3個線程執行3個任務,併發數就是3
最大併發數:同一時間最多隻能執行的任務的個數。

最⼤大併發數的相關⽅方法

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
   說明:如果沒有設置最大併發數,那麼併發的個數是由系統內存和CPU決定的,可能內存多久開多一點,內存少就開少一點。
   注意:num的值並不代表線程的個數,僅僅代表線程的ID。
   提示:最大併發數不要亂寫(5以內),不要開太多,一般以2~3爲宜,因爲雖然任務是在子線程進行處理的,但是cpu處理這些過多的子線程可能會影響UI,讓UI變卡。

雖然NSOperationQueue類設計用於併發執行Operations,,你也可以強制單個queue一次只能執行一個Operation。setMaxConcurrentOperationCount:方法可以配置queue的最大併發操作數量。設爲1就表示queue每次只能執行一個操作, 設置最大並行數爲1時,所有op都會串行。但是並不代表只有一個線程,operation執行的順序仍然依賴於其它因素,比如operation是否準備好和operation的優先級等。因此串行化的operation queue並不等同於GCD中的串行dispatch queue

4.2 隊列的取消,暫停和恢復

  • 取消隊列的所有操作

    一旦添加到operation queue,queue就擁有了這個Operation對象並且不能被刪除,唯一能做的事情是取消。你可以調用Operation對象的cancel方法取消單個操作,也可以調用operation queue的cancelAllOperations方法取消當前queue中的所有操作。

    • (void)cancelAllOperations;
      提示:也可以調用NSOperation的- (void)cancel⽅法取消單個操作
  • 暫停和恢復隊列

    如果你想臨時暫停Operations的執行,可以使用queue的setSuspended:方法暫停queue。不過暫停一個queue不會導致正在執行的operation在任務中途暫停,只是簡單地阻止調度新Operation執行。你可以在響應用戶請求時,暫停一個queue來暫停等待中的任務。稍後根據用戶的請求,可以再次調用setSuspended:方法繼續queue中operation的執行。
    
    • (void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復隊列
    • (BOOL)isSuspended; //當前狀態
      需要強調的是,暫停和恢復的適用場合:在tableview界面,開線程下載遠程的網絡界面,對UI會有影響,使用戶體驗變差。那麼這種情況,就可以設置在用戶操作UI(如滾動屏幕)的時候,暫停隊列(不是取消隊列),停止滾動的時候,恢復隊列。

4.3 Operations的同步執行

操作放置到隊列中,默認異步執行。爲了最佳的性能,你應該設計你的應用儘可能地異步操作,讓應用在Operation正在執行時可以去處理其它事情。如果需要在當前線程中處理operation完成後的結果,可以使用NSOperation的waitUntilFinished方法阻塞當前線程,等待operation完成。通常我們應該避免編寫這樣的代碼,阻塞當前線程可能是一種簡便的解決方案,但是它引入了更多的串行代碼,限制了整個應用的併發性,同時也降低了用戶體驗。絕對不要在應用主線程中等待一個Operation,只能在第二或次要線程中等待。阻塞主線程將導致應用無法響應用戶事件,應用也將表現爲無響應。

除了等待單個Operation完成,你也可以同時等待一個queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一個queue時,應用的其它線程仍然可以往queue中添加Operation,因此可能會加長線程的等待時間。

5. 小結:

使用NSOperationQueue來實現多線程編程是iOS常用的三中多線程編程工具中理解起來最簡單的,而且我們發現操作方法也並不是太多。但是NSOperationQueue實現的功能並不是太多,這可能也是它並不流行的一個原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章