細說 NSOperation

本文詳細介紹了在實現同步、異步 NSOperation 時分別須要實現哪些方法、注意哪些問題。最後對 GCD 與 NSOperation Queue 做了一個簡單的對比。html

本文同時發表於個人我的博客ios

Overview


在 iOS 中實現併發編程主要有三種方式:GCD、NSOperation Queue以及Thread,其中前二者使用普遍。 在正式開始以前有必要區分兩組概念:同步、異步與串行、並行。git

  • 同步(Synchronous)、異步(Asynchronous)一般指方法(或函數),同步方法表示直到任務完成才返回(如:dispatch_sync),異步方法則是將任務拋出去,在任務沒完成前就返回(如:dispatch_async);
  • 串行(Serial)、並行(Concurrent)一般指 App 執行一組任務的模式,串行表示一次只能執行一個任務,只有當前一個任務完成後才啓動下一個任務,而並行指能夠同時執行多個任務。最多見的莫過於 GCD 中的串行、並行隊列。

NSOperation Queue + NSOperation 做爲 iOS 中『高級的、面向對象的併發編程方式』耳熟能詳,但具體到一些細節問題上認識每每又比較模糊。本文在蘋果官方文檔 Concurrency Programming GuideNSOperation Class Reference 以及 NSOperationQueue Class Reference 的基礎上作了一次疏理和總結。github

NSOperation


NSOperation 自己是個抽象類,在使用前必須子類化(系統預約義了兩個子類:NSInvocationOperationNSBlockOperation)。那問題來了,在子類化過程當中,須要重寫父類的哪些方法?編程

這首先就要了解一下NSOperation類中幾個重要方法的默認實現: 安全

在 NSOperation 中還有一個重要概念:operation 的狀態,而且當狀態變化時須要經過 KVO 的方式通知外:
回到前面那個問題:子類化 NSOperation 時須要重寫哪些方法? 這取決於子類化後的 operation 是 Synchronous 仍是 Asynchronous(NSOperation 默認是Synchronous)。

Synchronous VS. Asynchronous Operations


因爲操做 NSOperation 與 NSOperation 任務的執行每每在不一樣的線程上進行,在繼續以前須要強調線程安全問題:『NSOperation 自己是 thread-safe,當咱們在子類重寫或自定義方法時一樣須要保證 thread-safe』。網絡

Synchronous Operations

對於 Synchronous Operation,在調用其 start 方法的線程上同步執行該 operation 的任務,start 方法返回時 operation 執行完成。所以,對於 Synchronous Operation 通常只需重寫 main 方法便可(start方法的默認實現已實現相關 KVO 功能)。併發

Asynchronous Operations

然而對於 Asynchronous Operation,調用其 start 方法後,在 start 返回時 operation 的任務可能還沒完成(爲了實現異步,通常須要在其餘線程執行 operation 的具體任務)。所以 start 方法默認實現不能知足異步須要(默認實現會在start返回前將 isExecuting 置爲 NO、isFinished 置爲 YES,併產生 KVO 通知)。此時至少須要重寫如下方法:app

  • start: 咱們知道 NSOperation 自己不具有併發(或者說異步執行)能力,所以須要 start 方法來實現,能夠經過建立子線程或其餘異步方式完成。同時須要在任務開始前將 isExecuting 置爲YES 並拋出 KVO 通知。 『重寫的 start 方法必定不能調用 [super start]
  • asynchronous 返回 YES,通常不須要拋出 KVO 通知
  • executing 返回 operation 的執行狀態,在其值發生變化時須要在 isExecuting 上拋出 KVO 通知
  • finished 返回 operation 的完成狀態,一樣值變化時須要在 isFinished 上拋出 KVO 通知

這裏咱們看看著名的網絡框架 AFNetworking 中關於 NSOperation 的使用:框架

AFNetworking 3.0 全面使用 NSURLSession,而 NSURLSession 自己是異步的、且沒有 NSURLConnection 須要 runloop 配合的問題,所以在3.0版本中並無使用 NSOperation,代碼獲得很大的簡化。這裏咱們說的是 AFNetworking 2.3.1 版本。

在 AFNetworking 中 AFURLConnectionOperation 是個異步的 NSOperation 子類,其 start 方法以下:

從上面 start 方法的實現能夠看到:

  1. 用 lock(遞歸鎖) 保證了thread-safe;
  2. 檢查了 operation 是否已被 cancel;
  3. 檢查了 operation 是否已 ready;
  4. 經過子線程實現併發;
  5. 在 state setter 中實現了 KVO。
    再來看看 AFURLConnectionOperation 使用的子線程:
    能夠看到,全部 AFURLConnectionOperation 實例底層使用的是同一個子線程,並在該線程中啓動了 runloop(NSURLConnection 的網絡回調必需要有 runloop 的配合,經過port-based input source 喚醒 runloop 處理網絡事件),也就是說 AFURLConnectionOperation 是在一條常駐子線程中處理網絡回調。

前面咱們提到 operation 被 cancel 時也被認爲是完成,這點在自定義 start 時一樣須要注意:

在 AFURLConnectionOperation 的 cancelConnection 以及 connection:didFailWithError: 方法中都會調用其 finish 方法:
ps:雖然 NSOperation 支持 cancel,但在調用 cancel 方法後該如何處理徹底由咱們自定義的 start 方法決定(固然良好的設計應該要符合 cancel 的語義)。

同時,AFURLConnectionOperation 也實現瞭如下方法:

關於 NSOperation 其餘細節問題


  • dependencies: 咱們能夠在 operation 間添加依賴關係,在某個 operation 所依賴的 operations 完成以前,其一直處於未就緒狀態(isReady 爲 NO)。 須要注意的是,依賴關係是 operation 自身的狀態,也就是說有依賴關係的 operations 能夠處在不一樣的 NSOperationQueue 中。

  • isReady: isReady 默認實現主要處理 operation 間的依賴關係,當咱們自定義該方法時須要考慮 super 的值,如 AFURLConnectionOperation中關於 isReady 的實現:

  • qualityOfService: 用於表示 operation 在獲取系統資源時的優先級,默認值:NSQualityOfServiceBackground,咱們能夠根據須要給 operation 賦不一樣的優化級,如最高優化級:NSQualityOfServiceUserInteractive

  • queuePriority: 用於設置 operation 在 operation queue 中的相對優化級,同一 queue 中優化級高的 operation(isReady 爲 YES) 會被優先執行。須要注意區分qualityOfService(在系統層面,operation 與其餘線程獲取資源的優先級)與queuePriority(同一 queue 中 operation 間執行的優化級)的區別。 同時,須要注意dependencies(嚴格控制執行順序)與queuePriority(queue 內部相對優先級)的區別。

NSOperation Queue


NSOperation Queue 用於管理、執行 NSOperation,不管其中的 operation 是並行仍是串行,queue 都會在子線程(借用 GCD)中執行 operation。 從上小節咱們知道,實現異步 operation 比同步 operation 要複雜許多,所以若是打算將 operation 加入 queue 中,則徹底能夠將 operation 實現爲同步方式。 對於 queue 中已就緒的 operation,queue 會選擇 queuePriority 值最大的 operation 執行。

關於 NSOperation Queue 有兩點須要強調:

  • cancelAllOperations:用於取消隊列中的 operations,對 queue 中全部 operations 調用 cancel方法。(從上小節咱們知道,對 operation 調用 cancel 方法後的效果徹底由 operation 本身決定。cancel 惟一能影響的就是清除 operation 的依賴關係,使其當即能夠被執行)。此時 queue 並不會 remove 其中的 operations,remove 操做僅發生在 operation 完成時。
  • suspended:將該屬性置爲 YES,會阻止 queue 執行新的 operation,但已經在執行中的 operation 不受此影響。

GCD vs. NSOperation Queue


GCD 與 NSOperation Queue 做爲常見的併發編程方式,在使用時該如何選擇? 首先,對比一下咱們關心的幾個問題:

咱們能夠看到,NSOperation Queue 做爲高級 API,有不少 GCD 沒有的功能,如須要支持:控制併發數、取消、添加依賴關係等須要使用 NSOperation Queue。 另外,因爲 block 可複用性沒有 NSOperation 好,對於獨立性強、可複用性高的任務建議使用 NSOperation 實現。 固然,NSOperation 在使用時須要 sub-classing,工做量較大,對於簡單的任務使用 GCD 便可。

別忘了,咱們還有第三種選擇:NSThread。因爲使用 NSThread 時須要處理線程相關的問題,通常不多使用。但不管是 GCD 仍是 NSOperation Queue,其中的任務具體什麼時候執行是由系統控制的,對於實時性要求很高的任務則可使用 NSThread。

小結


本文簡單討論了在使用 NSOperation 時須要重寫哪些方法、注意哪些問題。同時也對 GCD 與 NSOperation Queue 做了簡單對比,在清楚了它們各自的特色以後再作選擇時會更加清晰。

參考資料

Concurrency Programming Guide

NSOperation Class Reference

NSOperationQueue Class Reference

相關文章
相關標籤/搜索