一直以來,并發(fā)都被視為 iOS 開發(fā)中的「洪水猛獸」。許多開發(fā)者都將其視為危險地帶,唯恐避之而不及。更有謠傳認為,多線程代碼應該盡力避免。筆者同意,如果你對并發(fā)的了解不夠深入,就容易造成危險。但是,危險往往是因為無知。想想吧,在人們的日常生活中,會經(jīng)歷多少危險的行為或活動?但是,一旦掌握其要領,也就是一碟小菜罷了。
并發(fā)就是一柄值得你學習使用并熟練掌握的雙刃劍。它能幫助你打造高效、迅捷、響應及時的應用。于此同時,一旦誤用,也會毫不留情地毀掉應用。因此,在開始編寫并發(fā)代碼之前,好好想想你為什么需要并發(fā),你需要哪個 API 來解決問題?在 iOS 開發(fā)中,可用的 API 有很多。在本教程中,我們將探討最常用的兩個 API——NSOperation 以及調(diào)度隊列。
為什么需要并發(fā)?
假設你是有經(jīng)驗的 iOS 開發(fā)老手,不論你要創(chuàng)建什么樣的應用,你都需要并發(fā)來提高應用的響應度與速度。以下是筆者總結(jié)的學習或使用并發(fā)能夠帶來的好處:
利用 iOS 設備的硬件:現(xiàn)在,所有的 iOS 設備配備多核處理器,允許開發(fā)者并行執(zhí)行多個任務。你應該通過此功能好好利用這些硬件。
更好的用戶體驗:你很可能編寫了調(diào)用 Web 服務,處理 IO,或執(zhí)行一些繁重任務的代碼。你也知道,在 UI 線程執(zhí)行這些操作會凍結(jié)應用,使其無法響應用戶的行為。一旦用戶遭遇這類情況,他們的第一反應往往是結(jié)束應用。有了并發(fā)機制,這些任務都可以在背景線程中執(zhí)行,而無需暫停主線程或煩擾到用戶。用戶可以點擊應用中的按鈕,滾動瀏覽或跳轉(zhuǎn)目錄,與此同時,那些繁重的加載任務則放到后臺處理。
NSOperation 與調(diào)度隊列這類 API 簡化了并發(fā)的使用:創(chuàng)建并管理線程并非易事。這也是大多數(shù)開發(fā)者一聽到并發(fā)、多線程代碼這類術(shù)語就大驚失色的原因。iOS 其實提供了許多易于使用的并發(fā) API,能大大簡化開發(fā)者的工作。你不必擔心創(chuàng)建線程或管理底層的部件,這些 API 會幫你搞定一切。使用這些 API 的另一個好處在于:它們能幫你輕易實現(xiàn)同步化,從而避免了競爭狀態(tài)。當多個線程視圖讀取共享資源時,就會形成競爭狀態(tài),導致意想不到的結(jié)果。使用同步機制,就能防止資源在多個線程間的共享。
What do You Need to Know about Concurrency?
關于并發(fā),你需要了解哪些內(nèi)容?
本文將會解釋理解并發(fā)所需的全部知識,徹底消除你對它的恐懼。首先,我們建議你了解一下塊(blocks)(Swift 中的閉包),因為它們在并發(fā) API 中廣泛使用。之后,我們會探討調(diào)度隊列與 NSOperations。我們會詳細介紹這些并發(fā)概念,它們的區(qū)別以及實現(xiàn)方法。
第一部分: GCD (Grand Central Dispatch)
GCD 是用于在系統(tǒng) Unix 層管理并發(fā)代碼、異步執(zhí)行操作最為常用的 API。GCD 提供并管理任務隊列。首先,了解一下隊列是什么。
什么是隊列?
隊列是以先進先出(FIFO)原則管理對象的數(shù)據(jù)結(jié)構(gòu)。隊列與戲院售票窗口外的隊伍很相似。戲票是以先到先得的次序售賣的。排在隊伍前面的人會在隊伍后面的人之前得到戲票。計算機科學中的隊列也遵循似的原理:第一個添加到隊列中的對象會第一個從隊列中移除。
Photo credit: FreeImages.com/Sigurd Decroos
圖片來源:FreeImages.com/Sigurd Decroos
調(diào)度隊列
調(diào)度隊列是在應用中實現(xiàn)異步、并發(fā)地執(zhí)行任務的簡單方法。在調(diào)度隊列中,應用產(chǎn)生的任務會以塊(代碼塊)的形式提交。目前,有兩種調(diào)度隊列:1、串行隊列(Serial Queues),2、并發(fā)隊列(Concurrent Queues)。在進一步了解兩種隊列的區(qū)別之前,你需要知道:分配給這兩種隊列的任務在執(zhí)行時所處的線程與創(chuàng)建任務的線程相獨立。換句話說,你創(chuàng)建了一些代碼塊,并將其提交給主線程中的調(diào)度隊列。但是,所有的任務(也即代碼塊)會在單獨的線程(而非主線程)中執(zhí)行。
串行隊列
如果你選擇創(chuàng)建串行隊列,該隊列每次只能執(zhí)行一個任務。同一個串行隊列中的所有任務都會相互尊重,依次執(zhí)行。然而,它們不會在意其他獨立隊列中的任務。這意味著,如果使用了多個串行隊列,仍有可能并發(fā)地執(zhí)行任務。例如,你可以創(chuàng)建兩個串行隊列,每個隊列每次都只會執(zhí)行一個任務,但是仍有可能出現(xiàn)兩個任務同時執(zhí)行的情況。
在管理共享資源時,串行隊列的用處極大。它能保證對共享資源的訪問是依次進行的,從而防止出現(xiàn)競爭狀態(tài)。設想,只有一個售票窗口,但是有一群人想買戲票的場景。此處,售票窗口的職員就是共享資源。如果該職員不得不同時服務所有購票者,場面一定非常混亂。為了應對這種場景,人們被要求排成一列(串行隊列),職員才能依次服務每位購票者。
不過,需要重申的是,這并不意味著戲院只能一次服務一名顧客。如果戲院開設兩個以上的售票窗口,就能同時服務三名顧客。也即,使用多個串行隊列,就能并行處理多項任務。
使用串行隊列的好處如下:
- 保證依次訪問共享資源,防止出現(xiàn)競爭狀態(tài)。
- 任務以可預測的次序執(zhí)行。當你向串行調(diào)度隊列提交多個任務時,任務的執(zhí)行次序與其插入次序一致。
- 你可以創(chuàng)建任意數(shù)量的串行隊列。
并發(fā)隊列
顧名思義,并發(fā)隊列允許你并行執(zhí)行多個任務。任務開始執(zhí)行的次序遵照其加入隊列的次序。但是,任務執(zhí)行的過程都同步進行,不需要等待。并發(fā)隊列保證任務開始執(zhí)行的次序是確定的,但是你無法知道執(zhí)行的次序,執(zhí)行時長或在任意時間點同步執(zhí)行的任務個數(shù)。
比如,你向某個并發(fā)隊列提交了三個任務(任務1、2、3號)。這些任務會并發(fā)執(zhí)行,開始執(zhí)行的次序依照他們加入隊列的次序。然而,它們的執(zhí)行時長與完成時間并不一致。盡管任務2、3開始執(zhí)行的時間比任務1晚,但它們?nèi)杂锌赡茉谌蝿?之前完成執(zhí)行。最終,由系統(tǒng)決定任務執(zhí)行的情況。
使用隊列
了解了串行隊列與并發(fā)隊列的基本知識之后,現(xiàn)在來看看如何使用它們。默認情況下,系統(tǒng)為每個應用提供了一個串行隊列與四個并發(fā)隊列。主調(diào)度隊列是全局可用的串行隊列,在應用的主線程上執(zhí)行任務。該隊列用于更新應用的 UI,執(zhí)行與 UIViews 更新相關的所有任務。因此每次只能執(zhí)行一個任務,所以當你在主隊列運行繁重的任務時,UI 就會停止響應。
除了主隊列,系統(tǒng)還提供了四個并發(fā)隊列。我們稱之為 Global Dispatch(全局調(diào)度)隊列。這些隊列對應用而言是全局的,差別只在于優(yōu)先級的不同。為了使用這些隊列,你必須用 dispatch_get_global_queue 方法取得你偏好隊列的引用。該 dispatch_get_global_queue 方法的首個參數(shù)必須為下面四個值中的一個:
這些隊列類型代表了執(zhí)行的優(yōu)先次序。HIGH 隊列的優(yōu)先級最高,而 BACKGROUND 隊列的優(yōu)先級最低。你可以根據(jù)任務的優(yōu)先級決定使用何種優(yōu)先級的隊列。此外,這些隊列也會為蘋果的 API 所用,因此,你的任務并不是隊列中的所有任務。
最后,你可以創(chuàng)建任意數(shù)量的串行隊列或并發(fā)隊列。當用到并發(fā)隊列時,筆者強烈建議你使用這四個全局隊列。當然,你也可以自己創(chuàng)建并發(fā)隊列。
GCD 備忘錄
現(xiàn)在,你應該對調(diào)度隊列有了基本的理解。接下來,筆者將提供你一份簡單的 GCD 備忘錄以供參考。該備忘錄非常簡單,但是包含了有關 GCD 的林林總總,都是你用得上的知識。
很贊,對吧?接下來,我們會通過一個簡單的演示程序展示如何使用調(diào)度隊列。筆者會教你如果使用調(diào)度隊列優(yōu)化應用性能,提高應用響應度。
演示項目
我們的啟動項目非常簡單,主要展示四個圖片視圖,每個視圖都需要從一個遠程站點獲取圖片。圖片請求會在主線程中完成。為了展示這個過程對 UI 響應性能的影響,筆者在圖片下面添加了一個簡單的滑動條。現(xiàn)在,下載并運行該啟動項目。點擊 Start 按鈕開始下載圖片,在此過程中拖動滑塊。你會發(fā)現(xiàn),根本無法拖動它。
一旦點擊了 Start 按鈕,圖片就會在主線程中開始下載。顯然,這種方法非常糟糕,會導致 UI 停止響應。不幸的是,直到今天,仍有許多應用在主線程中執(zhí)行這類繁重的任務。下面,我們將使用調(diào)度隊列解決這一問題。
首先,我們會用并發(fā)隊列實現(xiàn)解決方案。之后,使用串行隊列再此實現(xiàn)解決方案。
使用并發(fā)調(diào)度隊列
現(xiàn)在,回到 Xcode 項目中的 ViewController.swift 文件。如果查看代碼,你會發(fā)現(xiàn)一名為 didClickOnStart 的動作方法。該方法會處理圖片的下載,其實現(xiàn)方式如下:
@IBAction func didClickOnStart(sender: AnyObject) {
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
self.imageView1.image = img1
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
self.imageView2.image = img2
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
self.imageView3.image = img3
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
self.imageView4.image = img4
}
每個 downloader 都會被視作一個任務,所有的任務都在主隊列中執(zhí)行。現(xiàn)在,換一種實現(xiàn)方式。首先,獲取一個默認優(yōu)先級的全局并發(fā)隊列的引用。
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) { () -> Void in
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
dispatch_async(dispatch_get_main_queue(), {
self.imageView1.image = img1
})
}
此處,我們先用 dispatch_get_global_queue 方法獲得默認并發(fā)隊列的引用。之后,在代碼塊內(nèi)部,提交下載第一張圖片的任務。圖片下載完成之后,向主線程提交另一個任務,用下載好的圖片更新圖片視圖。換句話說,我們將圖片下載任務放到后臺線程中進行,但是在主線程中執(zhí)行與 UI 相關的任務。
@IBAction func didClickOnStart(sender: AnyObject) {
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) { () -> Void in
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
dispatch_async(dispatch_get_main_queue(), {
self.imageView1.image = img1
})
}
dispatch_async(queue) { () -> Void in
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
dispatch_async(dispatch_get_main_queue(), {
self.imageView2.image = img2
})
}
dispatch_async(queue) { () -> Void in
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
dispatch_async(dispatch_get_main_queue(), {
self.imageView3.image = img3
})
}
dispatch_async(queue) { () -> Void in
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
dispatch_async(dispatch_get_main_queue(), {
self.imageView4.image = img4
})
}
}
將四張圖片的下載作為并發(fā)任務提交給默認隊列后,構(gòu)造并運行應用,運行速度應該會明顯改善(如果報出代碼錯誤,請仔細對照你的代碼與上面的代碼)。此外,在下載圖片的同時,滑動條應該也可以順利拖動,沒有任何延遲。
使用串行調(diào)度隊列
解決延遲問題的另一種辦法就是使用串行隊列。現(xiàn)在,回到 ViewController.swift 文件的 didClickOnStart() 方法。這一次,我們會使用串行隊列下載圖片。不過,在使用串行隊列時,你必須加倍注意自己引用的是哪一個串行隊列。每個應用都有一個默認的串行隊列,該隊列其實是用于 UI 加載的主隊列。因此,在使用串行隊列時,你必須創(chuàng)建一個新隊列,否則,在執(zhí)行自身任務的同時,應用也會試圖執(zhí)行更新 UI 的任務。這會導致錯誤與延遲,進而損害用戶體驗。你可以使用 dispatch_queue_create 方法創(chuàng)建一個新的隊列,并將所有任務提交給這個隊列,方法與之前介紹的相同。完成這些改動之后,代碼如下:
@IBAction func didClickOnStart(sender: AnyObject) {
let serialQueue = dispatch_queue_create("com.appcoda.imagesQueue", DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQueue) { () -> Void in
let img1 = Downloader .downloadImageWithURL(imageURLs[0])
dispatch_async(dispatch_get_main_queue(), {
self.imageView1.image = img1
})
}
dispatch_async(serialQueue) { () -> Void in
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
dispatch_async(dispatch_get_main_queue(), {
self.imageView2.image = img2
})
}
dispatch_async(serialQueue) { () -> Void in
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
dispatch_async(dispatch_get_main_queue(), {
self.imageView3.image = img3
})
}
dispatch_async(serialQueue) { () -> Void in
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
dispatch_async(dispatch_get_main_queue(), {
self.imageView4.image = img4
})
}
}
如你所見,此方法與并發(fā)隊列案例的唯一不同是串行隊列的創(chuàng)建。當你再次創(chuàng)建并運行應用時,會發(fā)現(xiàn)圖片下載過程還是在后臺運行,因此 UI 交互不受影響。
不過,你會注意到兩點:
- 與并發(fā)隊列的案例相比,圖片下載時間有所延長。原因是每次只下載一張圖片。每個任務只有在前一個任務完成之后,才開始執(zhí)行。
- 圖片依次加載,分別為圖片1,圖片2,圖片3,圖片4。原因是串行隊列每次只執(zhí)行一個任務。
第二部分:操作隊列
我們知道,GCD 是允許開發(fā)者并發(fā)地執(zhí)行任務的底級別 C API。然而,操作隊列是隊列模型的高級抽象,基于 GCD 建立。這意味著,你可以像 GCD 那樣并發(fā)地執(zhí)行任務,卻是以面向?qū)ο蟮姆绞健:喍灾僮麝犃羞M一步簡化了開發(fā)者的工作。
與 GCD 不同,操作隊列不循序先進先出的次序。以下是操作隊列與調(diào)度隊列的不同之處:
不遵循 FIFO 次序:在操作隊列中,你可以為操作設定執(zhí)行優(yōu)先級,并添加操作間的依賴關系。也就是說,你可以定義一些操作只在另一些操作完成之后才能被執(zhí)行。這也是他們不遵循先進先出原則的原因。
默認情況下,操作隊列并發(fā)運行:盡管不能將其類型改為串行隊列,你仍能使用操作間的依賴關系指定任務的執(zhí)行順序。
操作隊列是 NSOperationQueue 類的實例,其任務則封裝在 NSOperation 的實例中。
NSOperation
NSOperation
如前所述,任務以 NSOperation 實例的形式提交給操作隊列。而在 GCD 的討論中,我們說過任務以塊為單位進行提交。此處也一樣,不過任務必須捆綁為 NSOperation 實例。你可以簡單地將 NSOperation 視為一個工作單元。
NSOperation 是抽象類,因此無法直接使用。所以,你只能使用 NSOperation 的子類。在 iOS SDK 中,提供了兩個 NSOperation 的具體子類。這些類可以直接使用,不過,你也可以自行創(chuàng)建 NSOperation 的子類來執(zhí)行操作。我們可以直接使用的兩個類為:
- NSBlockOperation —— 使用此類可創(chuàng)建帶有一個或多個塊的操作。操作本身可包含多個塊,而且只有當所有塊都執(zhí)行完畢時,該操作才算完成。
- NSInvocationOperation —— 使用此類創(chuàng)建的操作能夠針對特定對象喚起選擇器。
So what’s the advantages of NSOperation?
那么,NSOperation 有什么好處呢?
1.首先,借由 NSOperation 類中的 addDependency(op: NSOperation) 方法,他們支持依賴關系。當你想創(chuàng)建的操作依賴于另一個操作的執(zhí)行情況時,NSOperation 就能派上用場了。
2.其次,將 queuePriority 屬性的值設置為下列值中的某一個,你可以改變操作執(zhí)行的優(yōu)先級。
public enum NSOperationQueuePriority : Int {
case VeryLow
case Low
case Normal
case High
case VeryHigh
}
The operations with high priority will be executed first.
優(yōu)先級高的操作會首先執(zhí)行。
3.你可以取消任意隊列中的某個操作或所有操作。操作在添加到隊列之后仍可以取消,調(diào)用 NSOperation 類中的 cancel() 方法即可。當你選擇取消操作時,可能發(fā)生的場景如下:
- 若操作已經(jīng)結(jié)束,cancel 方法就無法起效。
- 若操作正在被執(zhí)行,系統(tǒng)不會強制停止操作代碼。但是,cancelled(已取消)屬性會設置為真。
- 若操作還在隊列中等待執(zhí)行,該操作就不會被執(zhí)行。
4.NSOperation 有三個很有用的布爾值屬性,非別為finished(已完成),cancelled(已取消),和 ready(準備就緒)。一旦操作執(zhí)行完畢,finished 會設置為真。而一旦操作取消,cancelled 會設置為真。若是操作即將被執(zhí)行,則 ready 會設置為真。
5.一旦任務完成,任何 NSOperation 都可以將完成塊設置為 called(已經(jīng)調(diào)用)。一旦 NSOperation 中的 finished 屬性設置為真,塊就會變?yōu)?called。
現(xiàn)在,讓我們用 NSOperationQueues 重寫演示項目。首先,在 ViewController 類中聲明此變量:
var queue = NSOperationQueue()
之后,用下面的代碼替代 didClickOnStart 方法。請查看我們是如何在 NSOperationQueue 中執(zhí)行操作的:
@IBAction func didClickOnStart(sender: AnyObject) {
queue = NSOperationQueue()
queue.addOperationWithBlock { () -> Void in
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView1.image = img1
})
}
queue.addOperationWithBlock { () -> Void in
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView2.image = img2
})
}
queue.addOperationWithBlock { () -> Void in
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView3.image = img3
})
}
queue.addOperationWithBlock { () -> Void in
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView4.image = img4
})
}
}
如你所見,此處使用了 addOperationWithBlock 方法用給定的塊(或者如 Swift 中所說,閉包)創(chuàng)建新的操作。其實非常簡單,不是么?在主隊列中執(zhí)行任務,我們可以用 NSOperationQueue (NSOperationQueue.mainQueue())提交想在主隊列中執(zhí)行的任務,而不是像使用 GCD 時那樣調(diào)用 dispatch_async 方法。
現(xiàn)在,你可以運行應用,簡單測試一下。如果代碼輸入正確,應用應該在后臺下載圖片,不影響用戶交互界面。
在前面的例子里,我們借助 addOperationWithBlock 方法往隊列中添加操作。現(xiàn)在,讓我們使用 NSBlockOperation 進行同樣的操作,與此同時,提供更多的功能與選擇,比如設置完成處理程序。這一次,didClickOnStart 方法的改寫如下:
@IBAction func didClickOnStart(sender: AnyObject) {
queue = NSOperationQueue()
let operation1 = NSBlockOperation(block: {
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView1.image = img1
})
})
operation1.completionBlock = {
print("Operation 1 completed")
}
queue.addOperation(operation1)
let operation2 = NSBlockOperation(block: {
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView2.image = img2
})
})
operation2.completionBlock = {
print("Operation 2 completed")
}
queue.addOperation(operation2)
let operation3 = NSBlockOperation(block: {
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView3.image = img3
})
})
operation3.completionBlock = {
print("Operation 3 completed")
}
queue.addOperation(operation3)
let operation4 = NSBlockOperation(block: {
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView4.image = img4
})
})
operation4.completionBlock = {
print("Operation 4 completed")
}
queue.addOperation(operation4)
}
針對每一個操作,我們都創(chuàng)建一個新的 NSBlockOperation 實例用于將任務封裝為塊。借助 NSBlockOperation,你還可以設置完成處理程序。現(xiàn)在,操作執(zhí)行完成之后,特定的完成處理程序就會被調(diào)用。此處,為了簡便起見,我們只是在日志中記錄一則簡單的消息,提示操作已經(jīng)完成。如果你運行演示項目,會在控制臺看到如下信息:
Operation 1 completed
Operation 3 completed
Operation 2 completed
Operation 4 completed
Canceling Operations
取消操作
如前所述,NSBlockOperation 允許你管理操作。現(xiàn)在,讓我們來學習如何取消一個操作。為此,首先要在導航欄添加一個名為 Cancel(取消)的按鈕。為了演示取消操作,我們將在操作2與操作1,以及操作3與操作2之間分別添加一個依賴關系。也即,操作2會在操作1完成之后開始執(zhí)行,而操作3會在操作2完成之后開始執(zhí)行。操作4不存在依賴關系,會并發(fā)執(zhí)行。要想取消這些操作,你只需調(diào)用 NSOperationQueue 的 cancelAllOperations() 方法即可。下面,在 ViewController 類中插入下面的方法:
@IBAction func didClickOnCancel(sender: AnyObject) {
self.queue.cancelAllOperations()
}
請記住,你需要把添加到導航欄的 Cancel 按鈕與 didClickOnCancel 方法相連接。為此,你可以回到 Main.storyboard 文件,打開連接檢查器(Connections Inspector)。之后,你會看到 Received Actions 一節(jié)下的分開 didSelectCancel() 方法。點擊并從空圓拖拽到 Cancel 欄按鈕。之后,參照如下代碼創(chuàng)建 didClickOnStart 方法中的依賴關系:
operation2.addDependency(operation1)
operation3.addDependency(operation2)
之后,修改操作1的完成塊,在日志中記錄取消的狀態(tài):
operation1.completionBlock = {
print("Operation 1 completed, cancelled:\(operation1.cancelled) ")
}
你也可以修改操作2、3、4的日志記錄語句,從而更深入地理解此過程。現(xiàn)在,建造并允許應用。點擊Start 按鈕之后,點擊 Cancel 按鈕。這樣,操作1完成后所有操作都會被取消,以下是運行結(jié)果:
- 由于操作1已經(jīng)被執(zhí)行了,取消無法起效。因此,cancelled 的值在日志中記為假,應用仍會展示圖片1。
- 如果點擊 Cancel 按鈕的速度足夠快,操作2會被取消。cancelAllOperations() 的調(diào)用會中斷操作2的執(zhí)行,因此圖片2下載失敗。
- 操作3已經(jīng)在隊列中,等待操作2執(zhí)行完成。因為它依賴于操作2的完成才能繼續(xù)執(zhí)行。但由于操作2被取消了,操作3也不會得到執(zhí)行,而是立即從隊列中移除。
- 操作4并未設置任何依賴關系。因此,它會并發(fā)執(zhí)行,成功下載圖片4。
Where to go from here?
下一步該做什么?
在本文中,筆者詳細介紹了 iOS 并發(fā)的概念以及實現(xiàn)方式。首先,筆者簡單介紹了并發(fā)的概念,闡釋了 GCD,以及創(chuàng)建串行與并發(fā)隊列的方式。進一步地,我們學習了NSOperationQueues。現(xiàn)在,你應該對 GCD 與 NSOperationQueues 的區(qū)別有清晰的了解。
若想了解有關 iOS 并發(fā)的更多知識,筆者推薦你學習蘋果的并發(fā)指南。
作為參考,你可以在此處下載前文提到的完整源碼。
Please feel free to ask any questions. I love to read your comment.
歡迎提問或留下意見及建議。
OneAPM Mobile Insight 以真實用戶體驗為度量標準進行 Crash 分析,監(jiān)控網(wǎng)絡請求及網(wǎng)絡錯誤,提升用戶留存。訪問 OneAPM 官方網(wǎng)站感受更多應用性能優(yōu)化體驗,想閱讀更多技術(shù)文章,請訪問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客