一直以來腊嗡,并發(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í)更新中苗傅。