iOS 并發(fā):NSOperation 與調(diào)度隊(duì)列入門

一直以來腊嗡,并發(fā)都被視為 iOS 開發(fā)中的「洪水猛獸」。許多開發(fā)者都將其視為危險(xiǎn)地帶拾酝,唯恐避之而不及燕少。更有謠傳認(rèn)為,多線程代碼應(yīng)該盡力避免蒿囤。筆者同意客们,如果你對并發(fā)的了解不夠深入,就容易造成危險(xiǎn)材诽。但是底挫,危險(xiǎn)往往是因?yàn)闊o知。想想吧脸侥,在人們的日常生活中建邓,會(huì)經(jīng)歷多少危險(xiǎn)的行為或活動(dòng)?但是睁枕,一旦掌握其要領(lǐng)官边,也就是一碟小菜罷了沸手。

并發(fā)就是一柄值得你學(xué)習(xí)使用并熟練掌握的雙刃劍。它能幫助你打造高效注簿、迅捷、響應(yīng)及時(shí)的應(yīng)用诡渴。于此同時(shí),一旦誤用玩徊,也會(huì)毫不留情地毀掉應(yīng)用。因此泣棋,在開始編寫并發(fā)代碼之前畔塔,好好想想你為什么需要并發(fā),你需要哪個(gè) API 來解決問題把敢?在 iOS 開發(fā)中,可用的 API 有很多谅辣。在本教程中修赞,我們將探討最常用的兩個(gè) API——NSOperation 以及調(diào)度隊(duì)列。


為什么需要并發(fā)桑阶?

假設(shè)你是有經(jīng)驗(yàn)的 iOS 開發(fā)老手柏副,不論你要?jiǎng)?chuàng)建什么樣的應(yīng)用,你都需要并發(fā)來提高應(yīng)用的響應(yīng)度與速度蚣录。以下是筆者總結(jié)的學(xué)習(xí)或使用并發(fā)能夠帶來的好處:

利用 iOS 設(shè)備的硬件:現(xiàn)在割择,所有的 iOS 設(shè)備配備多核處理器,允許開發(fā)者并行執(zhí)行多個(gè)任務(wù)萎河。你應(yīng)該通過此功能好好利用這些硬件荔泳。

更好的用戶體驗(yàn):你很可能編寫了調(diào)用 Web 服務(wù),處理 IO虐杯,或執(zhí)行一些繁重任務(wù)的代碼玛歌。你也知道,在 UI 線程執(zhí)行這些操作會(huì)凍結(jié)應(yīng)用擎椰,使其無法響應(yīng)用戶的行為支子。一旦用戶遭遇這類情況,他們的第一反應(yīng)往往是結(jié)束應(yīng)用确憨。有了并發(fā)機(jī)制译荞,這些任務(wù)都可以在背景線程中執(zhí)行瓤的,而無需暫停主線程或煩擾到用戶。用戶可以點(diǎn)擊應(yīng)用中的按鈕吞歼,滾動(dòng)瀏覽或跳轉(zhuǎn)目錄圈膏,與此同時(shí),那些繁重的加載任務(wù)則放到后臺(tái)處理篙骡。

NSOperation 與調(diào)度隊(duì)列這類 API 簡化了并發(fā)的使用:創(chuàng)建并管理線程并非易事。這也是大多數(shù)開發(fā)者一聽到并發(fā)尿褪、多線程代碼這類術(shù)語就大驚失色的原因杖玲。iOS 其實(shí)提供了許多易于使用的并發(fā) API摆马,能大大簡化開發(fā)者的工作囤采。你不必?fù)?dān)心創(chuàng)建線程或管理底層的部件,這些 API 會(huì)幫你搞定一切代虾。使用這些 API 的另一個(gè)好處在于:它們能幫你輕易實(shí)現(xiàn)同步化褐着,從而避免了競爭狀態(tài)。當(dāng)多個(gè)線程視圖讀取共享資源時(shí)项郊,就會(huì)形成競爭狀態(tài)着降,導(dǎo)致意想不到的結(jié)果任洞。使用同步機(jī)制妆偏,就能防止資源在多個(gè)線程間的共享钱骂。

What do You Need to Know about Concurrency?

關(guān)于并發(fā)见秽,你需要了解哪些內(nèi)容解取?

本文將會(huì)解釋理解并發(fā)所需的全部知識(shí),徹底消除你對它的恐懼伦忠。首先昆码,我們建議你了解一下塊(blocks)(Swift 中的閉包)赋咽,因?yàn)樗鼈冊诓l(fā) API 中廣泛使用。之后陪毡,我們會(huì)探討調(diào)度隊(duì)列與 NSOperations毡琉。我們會(huì)詳細(xì)介紹這些并發(fā)概念桅滋,它們的區(qū)別以及實(shí)現(xiàn)方法丐谋。

第一部分: GCD (Grand Central Dispatch)

GCD 是用于在系統(tǒng) Unix 層管理并發(fā)代碼师枣、異步執(zhí)行操作最為常用的 API践美。GCD 提供并管理任務(wù)隊(duì)列陨倡。首先,了解一下隊(duì)列是什么杂曲。

什么是隊(duì)列擎勘?

隊(duì)列是以先進(jìn)先出(FIFO)原則管理對象的數(shù)據(jù)結(jié)構(gòu)棚饵。隊(duì)列與戲院售票窗口外的隊(duì)伍很相似。戲票是以先到先得的次序售賣的欣硼。排在隊(duì)伍前面的人會(huì)在隊(duì)伍后面的人之前得到戲票诈胜。計(jì)算機(jī)科學(xué)中的隊(duì)列也遵循似的原理:第一個(gè)添加到隊(duì)列中的對象會(huì)第一個(gè)從隊(duì)列中移除耘斩。

調(diào)度隊(duì)列

調(diào)度隊(duì)列是在應(yīng)用中實(shí)現(xiàn)異步、并發(fā)地執(zhí)行任務(wù)的簡單方法岩饼。在調(diào)度隊(duì)列中版述,應(yīng)用產(chǎn)生的任務(wù)會(huì)以塊(代碼塊)的形式提交渴析。目前俭茧,有兩種調(diào)度隊(duì)列:1、串行隊(duì)列(Serial Queues)毡们,2衙熔、并發(fā)隊(duì)列(Concurrent Queues)青责。在進(jìn)一步了解兩種隊(duì)列的區(qū)別之前,你需要知道:分配給這兩種隊(duì)列的任務(wù)在執(zhí)行時(shí)所處的線程與創(chuàng)建任務(wù)的線程相獨(dú)立产阱。換句話說构蹬,你創(chuàng)建了一些代碼塊庄敛,并將其提交給主線程中的調(diào)度隊(duì)列藻烤。但是怖亭,所有的任務(wù)(也即代碼塊)會(huì)在單獨(dú)的線程(而非主線程)中執(zhí)行期吓。

串行隊(duì)列

如果你選擇創(chuàng)建串行隊(duì)列讨勤,該隊(duì)列每次只能執(zhí)行一個(gè)任務(wù)悬襟。同一個(gè)串行隊(duì)列中的所有任務(wù)都會(huì)相互尊重脊岳,依次執(zhí)行。然而亿驾,它們不會(huì)在意其他獨(dú)立隊(duì)列中的任務(wù)莫瞬。這意味著疼邀,如果使用了多個(gè)串行隊(duì)列,仍有可能并發(fā)地執(zhí)行任務(wù)涨岁。例如蹬铺,你可以創(chuàng)建兩個(gè)串行隊(duì)列甜攀,每個(gè)隊(duì)列每次都只會(huì)執(zhí)行一個(gè)任務(wù)赴邻,但是仍有可能出現(xiàn)兩個(gè)任務(wù)同時(shí)執(zhí)行的情況姥敛。

在管理共享資源時(shí),串行隊(duì)列的用處極大了赌。它能保證對共享資源的訪問是依次進(jìn)行的袄秩,從而防止出現(xiàn)競爭狀態(tài)之剧。設(shè)想禁漓,只有一個(gè)售票窗口,但是有一群人想買戲票的場景畜挨。此處竹椒,售票窗口的職員就是共享資源。如果該職員不得不同時(shí)服務(wù)所有購票者赊窥,場面一定非诚悄埽混亂熄阻。為了應(yīng)對這種場景秃殉,人們被要求排成一列(串行隊(duì)列)钾军,職員才能依次服務(wù)每位購票者吏恭。

不過,需要重申的是搅幅,這并不意味著戲院只能一次服務(wù)一名顧客盏筐。如果戲院開設(shè)兩個(gè)以上的售票窗口,就能同時(shí)服務(wù)三名顧客漾抬。也即纳令,使用多個(gè)串行隊(duì)列,就能并行處理多項(xiàng)任務(wù)捏雌。

使用串行隊(duì)列的好處如下:

保證依次訪問共享資源性湿,防止出現(xiàn)競爭狀態(tài)叹括。

任務(wù)以可預(yù)測的次序執(zhí)行汁雷。當(dāng)你向串行調(diào)度隊(duì)列提交多個(gè)任務(wù)時(shí)少孝,任務(wù)的執(zhí)行次序與其插入次序一致稍走。

你可以創(chuàng)建任意數(shù)量的串行隊(duì)列。

并發(fā)隊(duì)列

顧名思義狐树,并發(fā)隊(duì)列允許你并行執(zhí)行多個(gè)任務(wù)。任務(wù)開始執(zhí)行的次序遵照其加入隊(duì)列的次序在塔。但是,任務(wù)執(zhí)行的過程都同步進(jìn)行,不需要等待零截。并發(fā)隊(duì)列保證任務(wù)開始執(zhí)行的次序是確定的麸塞,但是你無法知道執(zhí)行的次序,執(zhí)行時(shí)長或在任意時(shí)間點(diǎn)同步執(zhí)行的任務(wù)個(gè)數(shù)瞻润。

比如喘垂,你向某個(gè)并發(fā)隊(duì)列提交了三個(gè)任務(wù)(任務(wù)1甜刻、2、3號)正勒。這些任務(wù)會(huì)并發(fā)執(zhí)行得院,開始執(zhí)行的次序依照他們加入隊(duì)列的次序。然而,它們的執(zhí)行時(shí)長與完成時(shí)間并不一致赡麦。盡管任務(wù)2扒接、3開始執(zhí)行的時(shí)間比任務(wù)1晚,但它們?nèi)杂锌赡茉谌蝿?wù)1之前完成執(zhí)行。最終桃焕,由系統(tǒng)決定任務(wù)執(zhí)行的情況溃睹。

使用隊(duì)列

了解了串行隊(duì)列與并發(fā)隊(duì)列的基本知識(shí)之后咐吼,現(xiàn)在來看看如何使用它們俗冻。默認(rèn)情況下讥蔽,系統(tǒng)為每個(gè)應(yīng)用提供了一個(gè)串行隊(duì)列與四個(gè)并發(fā)隊(duì)列。主調(diào)度隊(duì)列是全局可用的串行隊(duì)列,在應(yīng)用的主線程上執(zhí)行任務(wù)贮竟。該隊(duì)列用于更新應(yīng)用的 UI确垫,執(zhí)行與 UIViews 更新相關(guān)的所有任務(wù)搬瑰。因此每次只能執(zhí)行一個(gè)任務(wù),所以當(dāng)你在主隊(duì)列運(yùn)行繁重的任務(wù)時(shí)雏吭,UI 就會(huì)停止響應(yīng)婚温。

除了主隊(duì)列,系統(tǒng)還提供了四個(gè)并發(fā)隊(duì)列氯质。我們稱之為 Global Dispatch(全局調(diào)度)隊(duì)列钉嘹。這些隊(duì)列對應(yīng)用而言是全局的揍堰,差別只在于優(yōu)先級的不同。為了使用這些隊(duì)列武学,你必須用 dispatch_get_global_queue 方法取得你偏好隊(duì)列的引用栏妖。該 dispatch_get_global_queue 方法的首個(gè)參數(shù)必須為下面四個(gè)值中的一個(gè):

*

這些隊(duì)列類型代表了執(zhí)行的優(yōu)先次序坟瓢。HIGH 隊(duì)列的優(yōu)先級最高沙合,而 BACKGROUND 隊(duì)列的優(yōu)先級最低炊甲。你可以根據(jù)任務(wù)的優(yōu)先級決定使用何種優(yōu)先級的隊(duì)列称开。此外蝠筑,這些隊(duì)列也會(huì)為蘋果的 API 所用,因此忆某,你的任務(wù)并不是隊(duì)列中的所有任務(wù)。

最后测暗,你可以創(chuàng)建任意數(shù)量的串行隊(duì)列或并發(fā)隊(duì)列。當(dāng)用到并發(fā)隊(duì)列時(shí)证逻,筆者強(qiáng)烈建議你使用這四個(gè)全局隊(duì)列乐埠。當(dāng)然,你也可以自己創(chuàng)建并發(fā)隊(duì)列瑟曲。

GCD 備忘錄

現(xiàn)在饮戳,你應(yīng)該對調(diào)度隊(duì)列有了基本的理解。接下來洞拨,筆者將提供你一份簡單的 GCD 備忘錄以供參考扯罐。該備忘錄非常簡單,但是包含了有關(guān) GCD 的林林總總烦衣,都是你用得上的知識(shí)歹河。



很贊,對吧花吟?接下來秸歧,我們會(huì)通過一個(gè)簡單的演示程序展示如何使用調(diào)度隊(duì)列。筆者會(huì)教你如果使用調(diào)度隊(duì)列優(yōu)化應(yīng)用性能衅澈,提高應(yīng)用響應(yīng)度键菱。

演示項(xiàng)目

我們的啟動(dòng)項(xiàng)目非常簡單,主要展示四個(gè)圖片視圖今布,每個(gè)視圖都需要從一個(gè)遠(yuǎn)程站點(diǎn)獲取圖片经备。圖片請求會(huì)在主線程中完成。為了展示這個(gè)過程對 UI 響應(yīng)性能的影響部默,筆者在圖片下面添加了一個(gè)簡單的滑動(dòng)條∏置桑現(xiàn)在,下載并運(yùn)行該啟動(dòng)項(xiàng)目傅蹂。點(diǎn)擊 Start 按鈕開始下載圖片纷闺,在此過程中拖動(dòng)滑塊。你會(huì)發(fā)現(xiàn)份蝴,根本無法拖動(dòng)它犁功。

一旦點(diǎn)擊了 Start 按鈕,圖片就會(huì)在主線程中開始下載婚夫。顯然波桩,這種方法非常糟糕,會(huì)導(dǎo)致 UI 停止響應(yīng)请敦。不幸的是镐躲,直到今天,仍有許多應(yīng)用在主線程中執(zhí)行這類繁重的任務(wù)侍筛。下面萤皂,我們將使用調(diào)度隊(duì)列解決這一問題。


首先匣椰,我們會(huì)用并發(fā)隊(duì)列實(shí)現(xiàn)解決方案裆熙。之后,使用串行隊(duì)列再此實(shí)現(xiàn)解決方案禽笑。

使用并發(fā)調(diào)度隊(duì)列

現(xiàn)在入录,回到 Xcode 項(xiàng)目中的 ViewController.swift 文件。如果查看代碼佳镜,你會(huì)發(fā)現(xiàn)一名為 didClickOnStart 的動(dòng)作方法僚稿。該方法會(huì)處理圖片的下載,其實(shí)現(xiàn)方式如下:


每個(gè) downloader 都會(huì)被視作一個(gè)任務(wù)蟀伸,所有的任務(wù)都在主隊(duì)列中執(zhí)行∈赐現(xiàn)在,換一種實(shí)現(xiàn)方式啊掏。首先蠢络,獲取一個(gè)默認(rèn)優(yōu)先級的全局并發(fā)隊(duì)列的引用。


此處迟蜜,我們先用 dispatch_get_global_queue 方法獲得默認(rèn)并發(fā)隊(duì)列的引用刹孔。之后,在代碼塊內(nèi)部娜睛,提交下載第一張圖片的任務(wù)髓霞。圖片下載完成之后,向主線程提交另一個(gè)任務(wù)微姊,用下載好的圖片更新圖片視圖酸茴。換句話說,我們將圖片下載任務(wù)放到后臺(tái)線程中進(jìn)行兢交,但是在主線程中執(zhí)行與 UI 相關(guān)的任務(wù)薪捍。


將四張圖片的下載作為并發(fā)任務(wù)提交給默認(rèn)隊(duì)列后,構(gòu)造并運(yùn)行應(yīng)用配喳,運(yùn)行速度應(yīng)該會(huì)明顯改善(如果報(bào)出代碼錯(cuò)誤酪穿,請仔細(xì)對照你的代碼與上面的代碼)。此外晴裹,在下載圖片的同時(shí)被济,滑動(dòng)條應(yīng)該也可以順利拖動(dòng),沒有任何延遲涧团。

使用串行調(diào)度隊(duì)列

解決延遲問題的另一種辦法就是使用串行隊(duì)列≈涣祝現(xiàn)在经磅,回到 ViewController.swift 文件的 didClickOnStart() 方法。這一次钮追,我們會(huì)使用串行隊(duì)列下載圖片预厌。不過,在使用串行隊(duì)列時(shí)元媚,你必須加倍注意自己引用的是哪一個(gè)串行隊(duì)列轧叽。每個(gè)應(yīng)用都有一個(gè)默認(rèn)的串行隊(duì)列,該隊(duì)列其實(shí)是用于 UI 加載的主隊(duì)列刊棕。因此炭晒,在使用串行隊(duì)列時(shí),你必須創(chuàng)建一個(gè)新隊(duì)列甥角,否則网严,在執(zhí)行自身任務(wù)的同時(shí)蜈膨,應(yīng)用也會(huì)試圖執(zhí)行更新 UI 的任務(wù)屿笼。這會(huì)導(dǎo)致錯(cuò)誤與延遲,進(jìn)而損害用戶體驗(yàn)翁巍。你可以使用 dispatch_queue_create 方法創(chuàng)建一個(gè)新的隊(duì)列驴一,并將所有任務(wù)提交給這個(gè)隊(duì)列,方法與之前介紹的相同灶壶。完成這些改動(dòng)之后肝断,代碼如下:


如你所見,此方法與并發(fā)隊(duì)列案例的唯一不同是串行隊(duì)列的創(chuàng)建驰凛。當(dāng)你再次創(chuàng)建并運(yùn)行應(yīng)用時(shí)胸懈,會(huì)發(fā)現(xiàn)圖片下載過程還是在后臺(tái)運(yùn)行,因此 UI 交互不受影響恰响。

不過趣钱,你會(huì)注意到兩點(diǎn):

1. 與并發(fā)隊(duì)列的案例相比,圖片下載時(shí)間有所延長胚宦。原因是每次只下載一張圖片首有。每個(gè)任務(wù)只有在前一個(gè)任務(wù)完成之后,才開始執(zhí)行枢劝。

2. 圖片依次加載井联,分別為圖片1,圖片2您旁,圖片3烙常,圖片4。原因是串行隊(duì)列每次只執(zhí)行一個(gè)任務(wù)鹤盒。

第二部分:操作隊(duì)列

我們知道蚕脏,GCD 是允許開發(fā)者并發(fā)地執(zhí)行任務(wù)的底級別 C API侦副。然而,操作隊(duì)列是隊(duì)列模型的高級抽象蝗锥,基于 GCD 建立跃洛。這意味著,你可以像 GCD 那樣并發(fā)地執(zhí)行任務(wù)终议,卻是以面向?qū)ο蟮姆绞健:喍灾谢龋僮麝?duì)列進(jìn)一步簡化了開發(fā)者的工作穴张。

與 GCD 不同,操作隊(duì)列不循序先進(jìn)先出的次序两曼。以下是操作隊(duì)列與調(diào)度隊(duì)列的不同之處:

不遵循 FIFO 次序:在操作隊(duì)列中皂甘,你可以為操作設(shè)定執(zhí)行優(yōu)先級,并添加操作間的依賴關(guān)系悼凑。也就是說偿枕,你可以定義一些操作只在另一些操作完成之后才能被執(zhí)行。這也是他們不遵循先進(jìn)先出原則的原因户辫。

默認(rèn)情況下渐夸,操作隊(duì)列并發(fā)運(yùn)行:盡管不能將其類型改為串行隊(duì)列,你仍能使用操作間的依賴關(guān)系指定任務(wù)的執(zhí)行順序渔欢。

操作隊(duì)列是 NSOperationQueue 類的實(shí)例墓塌,其任務(wù)則封裝在 NSOperation 的實(shí)例中。

NSOperation

NSOperation

如前所述奥额,任務(wù)以 NSOperation 實(shí)例的形式提交給操作隊(duì)列苫幢。而在 GCD 的討論中,我們說過任務(wù)以塊為單位進(jìn)行提交垫挨。此處也一樣韩肝,不過任務(wù)必須捆綁為 NSOperation 實(shí)例。你可以簡單地將 NSOperation 視為一個(gè)工作單元九榔。

NSOperation 是抽象類哀峻,因此無法直接使用。所以帚屉,你只能使用 NSOperation 的子類谜诫。在 iOS SDK 中,提供了兩個(gè) NSOperation 的具體子類攻旦。這些類可以直接使用喻旷,不過,你也可以自行創(chuàng)建 NSOperation 的子類來執(zhí)行操作牢屋。我們可以直接使用的兩個(gè)類為:

NSBlockOperation —— 使用此類可創(chuàng)建帶有一個(gè)或多個(gè)塊的操作且预。操作本身可包含多個(gè)塊槽袄,而且只有當(dāng)所有塊都執(zhí)行完畢時(shí),該操作才算完成锋谐。

NSInvocationOperation —— 使用此類創(chuàng)建的操作能夠針對特定對象喚起選擇器遍尺。

So what’s the advantages of NSOperation?

那么,NSOperation 有什么好處呢涮拗?

1.首先乾戏,借由 NSOperation 類中的 addDependency(op: NSOperation) 方法,他們支持依賴關(guān)系三热。當(dāng)你想創(chuàng)建的操作依賴于另一個(gè)操作的執(zhí)行情況時(shí)鼓择,NSOperation 就能派上用場了。

2.其次就漾,將 queuePriority 屬性的值設(shè)置為下列值中的某一個(gè)呐能,你可以改變操作執(zhí)行的優(yōu)先級。



The operations with high priority will be executed first.

優(yōu)先級高的操作會(huì)首先執(zhí)行抑堡。

3.你可以取消任意隊(duì)列中的某個(gè)操作或所有操作摆出。操作在添加到隊(duì)列之后仍可以取消,調(diào)用 NSOperation 類中的 cancel() 方法即可首妖。當(dāng)你選擇取消操作時(shí)偎漫,可能發(fā)生的場景如下:

若操作已經(jīng)結(jié)束,cancel 方法就無法起效悯搔。

若操作正在被執(zhí)行骑丸,系統(tǒng)不會(huì)強(qiáng)制停止操作代碼。但是妒貌,cancelled(已取消)屬性會(huì)設(shè)置為真通危。

若操作還在隊(duì)列中等待執(zhí)行,該操作就不會(huì)被執(zhí)行灌曙。

4.NSOperation 有三個(gè)很有用的布爾值屬性菊碟,非別為finished(已完成),cancelled(已取消)在刺,和 ready(準(zhǔn)備就緒)逆害。一旦操作執(zhí)行完畢,finished 會(huì)設(shè)置為真蚣驼。而一旦操作取消魄幕,cancelled 會(huì)設(shè)置為真。若是操作即將被執(zhí)行颖杏,則 ready 會(huì)設(shè)置為真纯陨。

5.一旦任務(wù)完成,任何 NSOperation 都可以將完成塊設(shè)置為 called(已經(jīng)調(diào)用)。一旦 NSOperation 中的 finished 屬性設(shè)置為真翼抠,塊就會(huì)變?yōu)?called咙轩。

現(xiàn)在,讓我們用 NSOperationQueues 重寫演示項(xiàng)目阴颖。首先活喊,在 ViewController 類中聲明此變量:


之后,用下面的代碼替代 didClickOnStart 方法量愧。請查看我們是如何在 NSOperationQueue 中執(zhí)行操作的:


如你所見钾菊,此處使用了 addOperationWithBlock 方法用給定的塊(或者如 Swift 中所說,閉包)創(chuàng)建新的操作侠畔。其實(shí)非常簡單结缚,不是么?在主隊(duì)列中執(zhí)行任務(wù)软棺,我們可以用 NSOperationQueue (NSOperationQueue.mainQueue())提交想在主隊(duì)列中執(zhí)行的任務(wù),而不是像使用 GCD 時(shí)那樣調(diào)用 dispatch_async 方法尤勋。

現(xiàn)在喘落,你可以運(yùn)行應(yīng)用,簡單測試一下最冰。如果代碼輸入正確瘦棋,應(yīng)用應(yīng)該在后臺(tái)下載圖片,不影響用戶交互界面暖哨。

在前面的例子里赌朋,我們借助 addOperationWithBlock 方法往隊(duì)列中添加操作。現(xiàn)在篇裁,讓我們使用 NSBlockOperation 進(jìn)行同樣的操作沛慢,與此同時(shí),提供更多的功能與選擇达布,比如設(shè)置完成處理程序团甲。這一次,didClickOnStart 方法的改寫如下:


針對每一個(gè)操作黍聂,我們都創(chuàng)建一個(gè)新的 NSBlockOperation 實(shí)例用于將任務(wù)封裝為塊躺苦。借助 NSBlockOperation,你還可以設(shè)置完成處理程序〔梗現(xiàn)在匹厘,操作執(zhí)行完成之后,特定的完成處理程序就會(huì)被調(diào)用脐区。此處愈诚,為了簡便起見,我們只是在日志中記錄一則簡單的消息,提示操作已經(jīng)完成扰路。如果你運(yùn)行演示項(xiàng)目尤溜,會(huì)在控制臺(tái)看到如下信息:


Canceling Operations

取消操作

如前所述,NSBlockOperation 允許你管理操作『钩現(xiàn)在宫莱,讓我們來學(xué)習(xí)如何取消一個(gè)操作。為此哩罪,首先要在導(dǎo)航欄添加一個(gè)名為 Cancel(取消)的按鈕授霸。為了演示取消操作,我們將在操作2與操作1际插,以及操作3與操作2之間分別添加一個(gè)依賴關(guān)系碘耳。也即,操作2會(huì)在操作1完成之后開始執(zhí)行框弛,而操作3會(huì)在操作2完成之后開始執(zhí)行辛辨。操作4不存在依賴關(guān)系,會(huì)并發(fā)執(zhí)行瑟枫。要想取消這些操作斗搞,你只需調(diào)用 NSOperationQueue 的 cancelAllOperations() 方法即可。下面慷妙,在 ViewController 類中插入下面的方法:


請記住僻焚,你需要把添加到導(dǎo)航欄的 Cancel 按鈕與 didClickOnCancel 方法相連接。為此膝擂,你可以回到 Main.storyboard 文件虑啤,打開連接檢查器(Connections Inspector)。之后架馋,你會(huì)看到 Received Actions 一節(jié)下的分開 didSelectCancel() 方法狞山。點(diǎn)擊并從空圓拖拽到 Cancel 欄按鈕。之后绩蜻,參照如下代碼創(chuàng)建 didClickOnStart 方法中的依賴關(guān)系:


之后铣墨,修改操作1的完成塊,在日志中記錄取消的狀態(tài):


你也可以修改操作2办绝、3伊约、4的日志記錄語句,從而更深入地理解此過程≡胁酰現(xiàn)在屡律,建造并允許應(yīng)用。點(diǎn)擊Start 按鈕之后降淮,點(diǎn)擊 Cancel 按鈕超埋。這樣搏讶,操作1完成后所有操作都會(huì)被取消,以下是運(yùn)行結(jié)果:

由于操作1已經(jīng)被執(zhí)行了霍殴,取消無法起效媒惕。因此,cancelled 的值在日志中記為假来庭,應(yīng)用仍會(huì)展示圖片1妒蔚。

如果點(diǎn)擊 Cancel 按鈕的速度足夠快,操作2會(huì)被取消月弛。cancelAllOperations() 的調(diào)用會(huì)中斷操作2的執(zhí)行肴盏,因此圖片2下載失敗。

操作3已經(jīng)在隊(duì)列中帽衙,等待操作2執(zhí)行完成菜皂。因?yàn)樗蕾囉诓僮?的完成才能繼續(xù)執(zhí)行。但由于操作2被取消了厉萝,操作3也不會(huì)得到執(zhí)行恍飘,而是立即從隊(duì)列中移除。

操作4并未設(shè)置任何依賴關(guān)系谴垫。因此常侣,它會(huì)并發(fā)執(zhí)行,成功下載圖片4弹渔。


Where to go from here?

下一步該做什么?

在本文中溯祸,筆者詳細(xì)介紹了 iOS 并發(fā)的概念以及實(shí)現(xiàn)方式肢专。首先,筆者簡單介紹了并發(fā)的概念焦辅,闡釋了 GCD博杖,以及創(chuàng)建串行與并發(fā)隊(duì)列的方式。進(jìn)一步地筷登,我們學(xué)習(xí)了NSOperationQueues√旮現(xiàn)在,你應(yīng)該對 GCD 與 NSOperationQueues 的區(qū)別有清晰的了解前方。

下圖為今年最新全套iOS開發(fā)的視頻教程狈醉,因?yàn)椴欢〞r(shí)更新中故不做多的截圖,如果有iOS開發(fā)上的問題不懂或者需要視頻教程可以看我的個(gè)人簡介惠险。

不定時(shí)更新中苗傅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市班巩,隨后出現(xiàn)的幾起案子渣慕,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逊桦,死亡現(xiàn)場離奇詭異眨猎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)强经,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門睡陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夕凝,你說我怎么就攤上這事宝穗。” “怎么了码秉?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵逮矛,是天一觀的道長。 經(jīng)常有香客問我转砖,道長须鼎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任府蔗,我火速辦了婚禮晋控,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姓赤。我一直安慰自己赡译,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布不铆。 她就那樣靜靜地躺著蝌焚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誓斥。 梳的紋絲不亂的頭發(fā)上只洒,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音劳坑,去河邊找鬼毕谴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛距芬,可吹牛的內(nèi)容都是我干的涝开。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔑穴,長吁一口氣:“原來是場噩夢啊……” “哼忠寻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起存和,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤奕剃,失蹤者是張志新(化名)和其女友劉穎衷旅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纵朋,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柿顶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了操软。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘁锯。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖聂薪,靈堂內(nèi)的尸體忽然破棺而出家乘,到底是詐尸還是另有隱情,我是刑警寧澤藏澳,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布仁锯,位于F島的核電站,受9級特大地震影響翔悠,放射性物質(zhì)發(fā)生泄漏业崖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一蓄愁、第九天 我趴在偏房一處隱蔽的房頂上張望双炕。 院中可真熱鬧,春花似錦撮抓、人聲如沸妇斤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趟济。三九已至,卻和暖如春咽笼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戚炫。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工剑刑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人双肤。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓施掏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茅糜。 傳聞我的和親對象是個(gè)殘疾皇子七芭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容