第43條:掌握GCD及操做隊列的使用時機

  本條要點:(做者總結)算法

  •  在解決多線程與任務管理問題時,派發隊列並不是惟一方案。
  •  操做隊列提供了一套高層的 Objective-C API,能實現純 GCD 所具有的絕大部份功能,並且還能完成一些更爲複雜的操做,那些操做若改用 GCD 來實現,則需另外編寫代碼。

  GCD 技術確實很棒,不過有時侯採用標準系統庫的組件,效果會更好。必定要了解每項技巧的使用時機,若是選錯了工具,那麼編出來的代碼就會難於維護。服務器

  不多有其餘技術能與GCD 的同步機制相媲美。對於那些只須要執行一次的代碼來講,也是如此,使用 GCD 的 dispatch_once 最爲方便。然而,在執行後臺任務時,GCD 並不必定是最佳方式。還有一種技術叫作 NSOperationQueue,它雖然與 GCD 不一樣,可是卻與之相關,開發者能夠把操做以 NSOperation 子類的形式放在隊列中,而這些操做也可以併發執行。其與 GCD 派發隊列有類似之處,這並不是巧合。「操做隊列」(operation Queue)在 GCD 以前就有了,其中某些設計原理因操做隊列而流行,GCD 就是基於這些原理構建的。實際上,從 iOS 4 與 Mac OSX 10.6 開始,操做隊列在底層是用 GCD 來實現的。數據結構

  在二者的諸多差異中,首先要注意:GCD 是純 C 的 API,而操做隊列則是 Objective-C 的對象。在 GCD 中,任務用塊來表示,而塊是個輕量級數據結構。與之相反,「操做」(operation)則是個更爲重量級的 Objective-C 對象。雖然說如此,但 GCD 並不老是最佳方案。有時候採用對象所帶來的開銷微乎其微,使用完整對象所帶來的好處反而大大超過其缺點。多線程

  用 NSOperationQueue 類的 「addOperationWithBlock:」 方法搭配 NSBlockOperation 類來使用操做隊列,其語法與純 GCD 方式很是相似。使用 NSOperation 及 NSOperationQueue 的好處以下:架構

  •  取消某個操做。若是使用操做隊列,那麼想要取消操做是很容易的。運行任務以前,能夠在 NSOperation 對象上調用 cancel 方法,該方法會設置對象內的標誌位,用以代表此任務不須要執行,不過,已經啓動的任務沒法取消。如果不使用操做隊列,而是把塊安排到 GCD 隊列中,那就沒法取消了。那套架構是 「安排好任務以後就無論了」(fire and forget)。開發者能夠在應用程序層本身來實現取消功能,不過這樣作須要編寫不少代碼,而那些代碼其實已經由操做隊列實現好了。
  • 指定操做間的依賴關係。一個操做能夠依賴其餘多個操做。開發者可以指定操做之間的依賴體系,使特定的操做必須在另一個操做順序執行完畢以後方可執行。比方說,從服務器下載並處理文件的動做,能夠用操做來表示,而在處理其餘文件以前,必須先下載 「清單文件」(manifest file)。後續的下載操做,都要依賴於先下載清單文件這一操做。若是操做隊列容許併發的話,那麼後續的多個下載操做就能夠同時執行,但前提是它們所依賴的那個清單文件下載操做已經執行完畢。
  • 經過鍵值觀測機制監控 NSOperation 對象的屬性。NSOperation 對象有許多屬性都適合經過鍵值觀測機制(簡稱KVO)來監聽,好比能夠經過 isCancelled 屬性來判斷任務是否已取消,又好比能夠經過 isFinished 屬性來判斷任務是否已完成。若是想在某個任務變動其狀態時獲得通知,或是想用比 GCD 更爲精細的方式來控制所要執行的任務,那麼鍵值觀測機制會頗有用。
  • 指定操做的優先級。操做的優先級表示此操做與隊列中其餘操做之間的優先關係。優先級高的操做先執行,優先級低的後執行。操做隊列的調度算法(scheduling algorithm)雖 「不透明」(opaque),但必然是通過一番深思熟慮才寫成的。反之,GCD 則沒有直接實現此功能的辦法。GCD 的隊列確實有優先級,不過那是針對整個隊列來講的,而不是針對每一個塊來講的。而令開發者在 GCD 之上本身來編寫調度算法,又不太合適,所以,在優先級這一點上,操做隊列所提供的功能要比 GCD 更爲便利。NSOperation 對象也有 「線程優先級」(thread priority),這決定了運行此操做的線程處在何種優先級上。用 GCD 也能夠實現此功能,然而採用操做隊列更爲簡單,只需設置一個屬性。
  • 重用 NSOperation 對象。系統內置了一些 NSOperation 的子類(好比 NSBlockOperation)供開發者調用,要是不想用這些固有子類的話,那就得本身來建立了。這些類就是普通的 Objective-C 對象,可以存聽任何信息。對象在執行時能夠充分利用存在於其中的信息,並且還能夠隨意調用定義在類中的方法。這就比派發隊列中那些簡單的塊要強大許多。這些 NSOperation 類能夠在代碼中屢次使用,它們符合軟件開發中的 「不重複」(Don't Repeat Yourself, DRY)原則。

  正如你們所見,操做隊列有不少地方賽過派發隊列。操做隊列提供了多種執行任務的方式,並且都是寫好了的,直接就能使用。開發者不用再編寫複雜的調度器,也不用本身來實現取消操做或指定操做優先級的功能,這些事情操做隊列都已經實現好了。併發

  有一個 API 選用了操做隊列而非派發隊列,這就是 NSNotificationCenter ,開發者可經過其中的方法來註冊監聽器,以便在發生相關事件時獲得通知,而這個方法接受的參數是塊,不是選擇子。方法原型以下:工具

1   - (id)addObserverForName:(NSString *)name object:(id)object queue:(NSOperationQueue *)queue usingBlock:(void(^)(NSNotification *))block;

  原本這個方法也能夠不使用操做隊列,而是把處理通知事件所用的塊安排在派發隊列裏。但實際上並無這麼作,其設計者顯然使用了高層的 Objective-C API。在這種狀況下,兩套方案的運行效率沒多大差距。設計這個方法的人可能不想使用派發隊列,由於那樣將依賴於 GCD,而這種依賴沒有必要,前面說過,塊自己和 GCD 無關,因此若是僅使用塊的話,就不會引入對 GCD 的依賴了。也有可能編寫這個方法的人想所有用 Objective-C 來描述,而不想使用純 C 的東西。性能

  常常會有人說:應該儘量選用高層 API,只在確有必要時才求助於底層。筆者也贊成這個說法,但我並不盲從。某些功能確實能夠用高層的 Objective-C 方法來作,但這並不等於說它就必定比底層實現方案好。要想肯定哪一種方案更佳,最好仍是要測試一下性能。測試

  ENDspa

相關文章
相關標籤/搜索