Operation Queues

iOS 并發(fā)編程之 Operation Queues - 雷純鋒的技術博客

術語

首先僵朗,我們先來了解一下在 iOS 并發(fā)編程中非常重要的三個術語,這是我們理解 iOS 并發(fā)編程的基礎:

進程(process)逗载,指的是一個正在運行中的可執(zhí)行文件襟铭。每一個進程都擁有獨立的虛擬內存空間和系統(tǒng)資源,包括端口權限等俊性,且至少包含一個主線程和任意數(shù)量的輔助線程搪桂。另外透敌,當一個進程的主線程退出時盯滚,這個進程就結束了;

線程(thread)酗电,指的是一個獨立的代碼執(zhí)行路徑魄藕,也就是說線程是代碼執(zhí)行路徑的最小分支。在 iOS 中撵术,線程的底層實現(xiàn)是基于POSIX threads API的背率,也就是我們常說的 pthreads ;

任務(task)嫩与,指的是我們需要執(zhí)行的工作寝姿,是一個抽象的概念,用通俗的話說划滋,就是一段代碼饵筑。

串行 vs. 并發(fā)

從本質上來說,串行和并發(fā)的主要區(qū)別在于允許同時執(zhí)行的任務數(shù)量处坪。串行根资,指的是一次只能執(zhí)行一個任務,必須等一個任務執(zhí)行完成后才能執(zhí)行下一個任務同窘;并發(fā)嫂冻,則指的是允許多個任務同時執(zhí)行。

同步 vs. 異步

同樣的塞椎,同步和異步操作的主要區(qū)別在于是否等待操作執(zhí)行完成,亦即是否阻塞當前線程睛低。同步操作會等待操作執(zhí)行完成后再繼續(xù)執(zhí)行接下來的代碼案狠,而異步操作則恰好相反,它會在調用后立即返回钱雷,不會等待操作的執(zhí)行結果骂铁。


隊列 vs. 線程

有一些對 iOS 并發(fā)編程模型不太了解的同學可能會對隊列和線程產生混淆,不清楚它們之間的區(qū)別與聯(lián)系罩抗,因此拉庵,我覺得非常有必要在這里簡單地介紹一下。在 iOS 中套蒂,有兩種不同類型的隊列钞支,分別是串行隊列和并發(fā)隊列。正如我們上面所說的操刀,串行隊列一次只能執(zhí)行一個任務烁挟,而并發(fā)隊列則可以允許多個任務同時執(zhí)行。iOS 系統(tǒng)就是使用這些隊列來進行任務調度的骨坑,它會根據(jù)調度任務的需要和系統(tǒng)當前的負載情況動態(tài)地創(chuàng)建和銷毀線程撼嗓,而不需要我們手動地管理。

iOS 的并發(fā)編程模型

在其他許多語言中,為了提高應用的并發(fā)性且警,我們往往需要自行創(chuàng)建一個或多個額外的線程粉捻,并且手動地管理這些線程的生命周期,這本身就已經是一項非常具有挑戰(zhàn)性的任務了斑芜。此外肩刃,對于一個應用來說,最優(yōu)的線程個數(shù)會隨著系統(tǒng)當前的負載和低層硬件的情況發(fā)生動態(tài)變化押搪。因此树酪,一個單獨的應用想要實現(xiàn)一套正確的多線程解決方案就變成了一件幾乎不可能完成的事情。而更糟糕的是大州,線程的同步機制大幅度地增加了應用的復雜性续语,并且還存在著不一定能夠提高應用性能的風險。

然而厦画,值得慶幸的是疮茄,在 iOS 中,蘋果采用了一種比傳統(tǒng)的基于線程的系統(tǒng)更加異步的方式來執(zhí)行并發(fā)任務根暑。與直接創(chuàng)建線程的方式不同力试,我們只需定義好要調度的任務,然后讓系統(tǒng)幫我們去執(zhí)行這些任務就可以了排嫌。我們可以完全不需要關心線程的創(chuàng)建與銷毀畸裳、以及多線程之間的同步等問題,蘋果已經在系統(tǒng)層面幫我們處理好了淳地,并且比我們手動地管理這些線程要高效得多怖糊。

因此,我們應該要聽從蘋果的勸告颇象,珍愛生命伍伤,遠離線程。不過話又說回來遣钳,盡管隊列是執(zhí)行并發(fā)任務的首先方式扰魂,但是畢竟它們也不是什么萬能的靈丹妙藥。所以蕴茴,在以下三種場景下劝评,我們還是應該直接使用線程的:

用線程以外的其他任何方式都不能實現(xiàn)我們的特定任務;

必須實時執(zhí)行一個任務倦淀。因為雖然隊列會盡可能快地執(zhí)行我們提交的任務付翁,但是并不能保證實時性;

你需要對在后臺執(zhí)行的任務有更多的可預測行為晃听。

Operation Queues vs. Grand Central Dispatch (GCD)

簡單來說百侧,GCD是蘋果基于C語言開發(fā)的砰识,一個用于多核編程的解決方案,主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱多處理系統(tǒng)佣渴。而 Operation Queues 則是一個建立在GCD的基礎之上的辫狼,面向對象的解決方案。它使用起來比GCD更加靈活辛润,功能也更加強大膨处。下面簡單地介紹了 Operation Queues 和GCD各自的使用場景:

Operation Queues :相對GCD來說,使用 Operation Queues 會增加一點點額外的開銷砂竖,但是我們卻換來了非常強大的靈活性和功能真椿,我們可以給 operation 之間添加依賴關系、取消一個正在執(zhí)行的 operation 乎澄、暫停和恢復 operation queue 等突硝;

GCD:則是一種更輕量級的,以FIFO的順序執(zhí)行并發(fā)任務的方式置济,使用GCD時我們并不關心任務的調度情況解恰,而讓系統(tǒng)幫我們自動處理。但是GCD的短板也是非常明顯的浙于,比如我們想要給任務之間添加依賴關系护盈、取消或者暫停一個正在執(zhí)行的任務時就會變得非常棘手。

關于 Operation 對象

在 iOS 開發(fā)中羞酗,我們可以使用 NSOperation 類來封裝需要執(zhí)行的任務腐宋,而一個 operation 對象(以下正文簡稱 operation )指的就是 NSOperation 類的一個具體實例。NSOperation 本身是一個抽象類檀轨,不能直接實例化脏款,因此,如果我們想要使用它來執(zhí)行具體任務的話裤园,就必須創(chuàng)建自己的子類或者使用系統(tǒng)預定義的兩個子類,NSInvocationOperation 和 NSBlockOperation 剂府。

NSInvocationOperation:我們可以通過一個object和selector非常方便地創(chuàng)建一個 NSInvocationOperation 拧揽,這是一種非常動態(tài)和靈活的方式。假設我們已經有了一個現(xiàn)成的方法腺占,這個方法中的代碼正好就是我們需要執(zhí)行的任務淤袜,那么我們就可以在不修改任何現(xiàn)有代碼的情況下,通過方法所在的對象和這個現(xiàn)有方法直接創(chuàng)建一個 NSInvocationOperation 衰伯。

NSBlockOperation:我們可以使用 NSBlockOperation 來并發(fā)執(zhí)行一個或多個 block 铡羡,只有當一個 NSBlockOperation 所關聯(lián)的所有 block 都執(zhí)行完畢時,這個 NSBlockOperation 才算執(zhí)行完成意鲸,有點類似于dispatch_group的概念烦周。

另外尽爆,所有的 operation 都支持以下特性:

支持在 operation 之間建立依賴關系,只有當一個 operation 所依賴的所有 operation 都執(zhí)行完成時读慎,這個 operation 才能開始執(zhí)行漱贱;

支持一個可選的 completion block ,這個 block 將會在 operation 的主任務執(zhí)行完成時被調用夭委;

支持通過KVO來觀察 operation 執(zhí)行狀態(tài)的變化幅狮;

支持設置執(zhí)行的優(yōu)先級,從而影響 operation 之間的相對執(zhí)行順序株灸;

支持取消操作崇摄,可以允許我們停止正在執(zhí)行的 operation 。

并發(fā) vs. 非并發(fā) Operation

通常來說慌烧,我們都是通過將 operation 添加到一個 operation queue 的方式來執(zhí)行 operation 的逐抑,然而這并不是必須的。我們也可以直接通過調用start方法來執(zhí)行一個 operation 杏死,但是這種方式并不能保證 operation 是異步執(zhí)行的泵肄。NSOperation 類的isConcurrent方法的返回值標識了一個 operation 相對于調用它的start方法的線程來說是否是異步執(zhí)行的。在默認情況下淑翼,isConcurrent 方法的返回值是NO腐巢,也就是說會阻塞調用它的start方法的線程。

如果我們想要自定義一個并發(fā)執(zhí)行的 operation 玄括,那么我們就必須要編寫一些額外的代碼來讓這個 operation 異步執(zhí)行冯丙。比如,為這個 operation 創(chuàng)建新的線程遭京、調用系統(tǒng)的異步方法或者其他任何方式來確保start方法在開始執(zhí)行任務后立即返回胃惜。

在絕大多數(shù)情況下,我們都不需要去實現(xiàn)一個并發(fā)的 operation 哪雕。如果我們一直是通過將 operation 添加到 operation queue 的方式來執(zhí)行 operation 的話船殉,我們就完全沒有必要去實現(xiàn)一個并發(fā)的 operation 冒掌。因為秤朗,當我們將一個非并發(fā)的 operation 添加到 operation queue 后,operation queue 會自動為這個 operation 創(chuàng)建一個線程献宫。因此堡僻,只有當我們需要手動地執(zhí)行一個 operation 糠惫,又想讓它異步執(zhí)行時,我們才有必要去實現(xiàn)一個并發(fā)的 operation 钉疫。

創(chuàng)建 NSInvocationOperation 對象

正如上面提到的硼讽,NSInvocationOperation 是 NSOperation 類的一個子類,當一個 NSInvocationOperation 開始執(zhí)行時牲阁,它會調用我們指定的object的selector方法固阁。通過使用 NSInvocationOperation 類壤躲,我們可以避免為每一個任務都創(chuàng)建一個自定義的子類,特別是當我們在修改一個已經存在的應用您炉,并且這個應用中已經有了我們需要執(zhí)行的任務所對應的object和selector時非常有用柒爵。

下面的示例代碼展示了如何通過object和selector創(chuàng)建一個 NSInvocationOperation 對象。說明赚爵,本文中的所有示例代碼都可以在這里OperationQueues找到棉胀,每一個類都有與之對應的測試類,充當client的角色冀膝,建議你在看完一個小節(jié)的代碼時唁奢,運行一下相應的測試用例,觀察打印的結果窝剖,以加深理解麻掸。

另外,我們在前面也提到了赐纱,NSInvocationOperation 類的使用可以非常的動態(tài)和靈活脊奋,其中比較顯著的一點就是我們可以根據(jù)上下文動態(tài)地調用object的不同selector。比如說疙描,我們可以根據(jù)用戶的輸入動態(tài)地執(zhí)行不同的selector:

創(chuàng)建 NSBlockOperation 對象

NSBlockOperation 是 NSOperation 類的另外一個系統(tǒng)預定義的子類诚隙,我們可以用它來封裝一個或多個block。我們知道GCD主要就是用來進行block調度的起胰,那為什么我們還需要 NSBlockOperation 類呢久又?一般來說,有以下兩個場景我們會優(yōu)先使用 NSBlockOperation 類:

當我們在應用中已經使用了 Operation Queues 且不想創(chuàng)建 Dispatch Queues 時效五,NSBlockOperation 類可以為我們的應用提供一個面向對象的封裝地消;

我們需要用到 Dispatch Queues 不具備的功能時,比如需要設置 operation 之間的依賴關系畏妖、使用KVO觀察 operation 的狀態(tài)變化等脉执。

自定義 Operation 對象

當系統(tǒng)預定義的兩個子類 NSInvocationOperation 和 NSBlockOperation 不能很好的滿足我們的需求時,我們可以自定義自己的 NSOperation 子類戒劫,添加我們想要的功能半夷。目前,我們可以自定義非并發(fā)和并發(fā)兩種不同類型的 NSOperation 子類谱仪,而自定義一個前者要比后者簡單得多。

對于一個非并發(fā)的 operation 否彩,我們需要做的就只是執(zhí)行main方法中的任務以及能夠正常響應取消事件就可以了疯攒,其它的復雜工作比如依賴配置、KVO 通知等 NSOperation 類都已經幫我們處理好了列荔。而對于一個并發(fā)的 operation 敬尺,我們還需要重寫 NSOperation 類中的一些現(xiàn)有方法枚尼。接下來,我們將會介紹如何自定義這兩種不同類型的 NSOperation 子類砂吞。

執(zhí)行主任務

從最低限度上來說署恍,每一個 operation 都應該至少實現(xiàn)以下兩個方法:

一個自定義的初始化方法;

main方法蜻直。

我們需要用一個自定義的初始化方法來將創(chuàng)建的 operation 置于一個已知的狀態(tài)盯质,并且重寫main方法來執(zhí)行我們的任務。當然概而,我們也可以實現(xiàn)一些其他的額外方法呼巷,比如實現(xiàn)NSCoding協(xié)議來允許我們歸檔和解檔 operation 等。下面的示例代碼展示了如何自定義一個簡單的 operation :

響應取消事件

當一個 operation 開始執(zhí)行后赎瑰,它會一直執(zhí)行它的任務直到完成或被取消為止王悍。我們可以在任意時間點取消一個 operation ,甚至是在它還未開始執(zhí)行之前餐曼。為了讓我們自定義的 operation 能夠支持取消事件压储,我們需要在代碼中定期地檢查isCancelled方法的返回值,一旦檢查到這個方法返回YES源譬,我們就需要立即停止執(zhí)行接下來的任務集惋。根據(jù)蘋果官方的說法,isCancelled方法本身是足夠輕量的瓶佳,所以就算是頻繁地調用它也不會給系統(tǒng)帶來太大的負擔芋膘。

The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.

通常來說,當我們自定義一個 operation 類時霸饲,我們需要考慮在以下幾個關鍵點檢查isCancelled方法的返回值:

在真正開始執(zhí)行任務之前为朋;

至少在每次循環(huán)中檢查一次,而如果一次循環(huán)的時間本身就比較長的話厚脉,則需要檢查得更加頻繁习寸;

在任何相對來說比較容易中止 operation 的地方。

看到這里傻工,我想你應該可以意識到一點霞溪,那就是盡管 operation 是支持取消操作的,但卻并不是立即取消的中捆,而是在你調用了 operation 的cancel方法之后的下一個isCancelled的檢查點取消的鸯匹。

執(zhí)行 Operation 對象

最終,我們需要執(zhí)行 operation 來調度與其關聯(lián)的任務泄伪。目前殴蓬,主要有兩種方式來執(zhí)行一個 operation :

將 operation 添加到一個 operation queue 中,讓 operation queue 來幫我們自動執(zhí)行蟋滴;

直接調用start方法手動執(zhí)行 operation 染厅。

添加 Operation 到 Operation Queue 中

就目前來說痘绎,將 operation 添加到 operation queue 中是最簡單的執(zhí)行 operation 的方式。另外肖粮,這里的 operation queue 指的就是 NSOperationQueue 類的一個具體實例孤页。就技術上而言,我們可以在應用中創(chuàng)建任意數(shù)量的 operation queue 涩馆,但是 operation queue 的數(shù)量越多并不意味著我們就能同時執(zhí)行越多的 operation 行施。因為同時并發(fā)的 operation 數(shù)量是由系統(tǒng)決定的,系統(tǒng)會根據(jù)當前可用的核心數(shù)以及負載情況動態(tài)地調整最大的并發(fā) operation 數(shù)量凌净。創(chuàng)建一個 operation queue 非常簡單悲龟,跟創(chuàng)建其他普通對象沒有任何區(qū)別:

NSOperationQueue*operationQueue=[[NSOperationQueuealloc]init];

創(chuàng)建好 operation queue 后,我們可以使用下面三個方法添加 operation 到 operation queue 中:

addOperation:冰寻,添加一個 operation 到 operation queue 中须教;

addOperations:waitUntilFinished:,添加一組 operation 到 operation queue 中斩芭;

addOperationWithBlock:轻腺,直接添加一個 block 到 operation queue 中,而不用創(chuàng)建一個 NSBlockOperation 對象划乖。

在大多數(shù)情況下贬养,一個 operation 被添加到 operation queue 后不久就會執(zhí)行,但是也有很多原因會使 operation queue 延遲執(zhí)行入隊的 operation 琴庵。比如误算,我們前面提到了的,如果一個 operation 所依賴的其他 operation 還沒有執(zhí)行完成時迷殿,這個 operation 就不能開始執(zhí)行儿礼;再比如說 operation queue 被暫停執(zhí)行或者已經達到了它最大可并發(fā)的 operation 數(shù)。下面的示例代碼展示了這三種方法的基本用法:

注意庆寺,在將一個 operation 添加到 operation queue 后就不要再修改這個 operation 了蚊夫。因為 operation 被添加到 operation queue 后隨時可能會執(zhí)行,這個是由系統(tǒng)決定的懦尝,所以再修改它的依賴關系或者所包含的數(shù)據(jù)就很有可能會造成未知的影響知纷。

盡管 NSOperationQueue 類是被設計成用來并發(fā)執(zhí)行 operation 的,但是我們也可以強制一個 operation queue 一次只執(zhí)行一個 operation 陵霉。我們可以通過setMaxConcurrentoperationCount:方法來設置一個 operation queue 最大可并發(fā)的 operation 數(shù)琅轧,因此將這個值設置成 1 就可以實現(xiàn)讓 operation queue 一次只執(zhí)行一個 operation 的目的。但是需要注意的是踊挠,雖然這樣可以讓 operation queue 一次只執(zhí)行一個 operation 乍桂,但是 operation 的執(zhí)行順序還是一樣會受其他因素影響的,比如 operation 的isReady狀態(tài)、operation 的隊列優(yōu)先級等模蜡。因此,一個串行的 operation queue 與一個串行的 dispatch queue 還是有本質區(qū)別的扁凛,因為 dispatch queue 的執(zhí)行順序一直是FIFO的忍疾。如果 operation 的執(zhí)行順序對我們來說非常重要,那么我們就應該在將 operation 添加到 operation queue 之前就建立好它的依賴關系谨朝。

總結

看到這里卤妒,我想你對 iOS 的并發(fā)編程模型已經有了一定的了解。正如文中所說的字币,我們應該盡可能地直接使用隊列而不是線程则披,讓系統(tǒng)去與線程打交道,而我們只需定義好要調度的任務就可以了洗出。一般情況下士复,我們也完全不需要去自定義一個并發(fā)的 operation ,因為在與 operation queue 結合使用時翩活,operation queue 會自動為非并發(fā)的 operation 創(chuàng)建一個線程阱洪。Operation Queues 是對GCD面向對象的封裝,它可以高度定制化菠镇,對依賴關系冗荸、隊列優(yōu)先級和線程優(yōu)先級等提供了很好的支持,是我們實現(xiàn)復雜任務調度時的不二之選利耍。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蚌本,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隘梨,更是在濱河造成了極大的恐慌程癌,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件出嘹,死亡現(xiàn)場離奇詭異席楚,居然都是意外死亡,警方通過查閱死者的電腦和手機税稼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門烦秩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人郎仆,你說我怎么就攤上這事只祠。” “怎么了扰肌?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵抛寝,是天一觀的道長。 經常有香客問我,道長盗舰,這世上最難降的妖魔是什么晶府? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮钻趋,結果婚禮上川陆,老公的妹妹穿的比我還像新娘。我一直安慰自己蛮位,他們只是感情好较沪,可當我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著失仁,像睡著了一般尸曼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萄焦,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天控轿,我揣著相機與錄音,去河邊找鬼拂封。 笑死解幽,一個胖子當著我的面吹牛,可吹牛的內容都是我干的烘苹。 我是一名探鬼主播躲株,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼镣衡!你這毒婦竟也來了霜定?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤廊鸥,失蹤者是張志新(化名)和其女友劉穎望浩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰说,經...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡磨德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吆视。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片典挑。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啦吧,靈堂內的尸體忽然破棺而出您觉,到底是詐尸還是另有隱情,我是刑警寧澤授滓,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布琳水,位于F島的核電站肆糕,受9級特大地震影響,放射性物質發(fā)生泄漏在孝。R本人自食惡果不足惜诚啃,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望私沮。 院中可真熱鬧绍申,春花似錦、人聲如沸顾彰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涨享。三九已至,卻和暖如春仆百,著一層夾襖步出監(jiān)牢的瞬間厕隧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工俄周, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吁讨,地道東北人。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓峦朗,卻偏偏與公主長得像建丧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子波势,可洞房花燭夜當晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內容