iOS 併發:從 NSOperation 和 Dispatch Queues 開始

做者:hossam ghareeb,原文連接,原文日期:2015-12-09
譯者:ray16897188;校對:Channe;定稿:千葉知風html

併發(Concurrency)在 iOS 開發中老是被看做是洪水猛獸通常。人們覺得它是一個很危險的領域,不少開發者都儘可能避免與其接觸。更有傳聞說你必定要竭盡所能的避免寫任何關於多線程的代碼。假如你對併發不是很瞭解卻還去使用它的話,那麼我贊成:併發是很危險的。只是它的危險是由於你不瞭解它。試想一下常人一輩子中體驗過的危險運動和行爲有多少,不少對吧?可是當掌握了以後,就會通通變成小菜一碟。併發是把雙刃劍,你應該掌握並學會如何去使用它。它能幫你寫出效率高、執行快、反應靈敏的 App,而與此同時,對它的濫用會無情的毀掉你的 App。這就是爲何在開始寫任何關於併發的代碼以前,先要想想你爲何要用到併發、須要用到哪一個(與併發有關的)API 來解決這個問題。iOS 中咱們有不少能用到的 API。在此教程裏我會講到最經常使用的兩個:NSOperationDispatch Queues(派發隊列)。ios

咱們爲何要用併發?

我知道你是一個有 iOS 背景的出色開發者。然而不管你要作一個什麼樣的 App,你都須要瞭解併發,讓你的 App 更快,更靈敏。我總結了一下學習和使用併發所帶來的優勢:git

  • 能更有效的利用 iOS 設備的硬件:如今全部的 iOS 設備都有一個多核處理器,可讓開發者並行執行多個任務。你應該好好利用這個特性,讓硬件優點發揮出來。github

  • 更好的用戶體驗:你可能寫過一些呼叫網絡服務、處理 IO,或者是其餘任何執行繁重任務的代碼。你也知道在 UI 所在的線程中作這些操做會把你的 App 凍結住,使它沒任何反應。當用戶遇到這種狀況時,他/她會絕不猶豫的直接殺掉或是關閉你的 App。而用併發的話,這些繁重的任務就會被安排到後臺去執行,不會佔滿主線程、干擾你用戶的操做。他們依舊可以點擊按鈕,來回拖動屏幕並在你的 App 中的每一個頁面之間跳轉,與此同時在後臺還處理着繁重的裝載任務。swift

  • NSOperation 和 Dispatch Queues 這樣的 API 讓使用併發變得容易:建立並管理線程並非簡單的事情。這也是爲何不少的開發者一聽到併發,還有多線程代碼這樣的術語時會感到懼怕的緣由。在 iOS 中咱們有強大而易用的併發 API,讓你的生活變得更來福。你無需再爲建立線程或管理底層的東西操心,API 會爲你搞定。這些 API 的另外一個優勢是它們能輕鬆幫你實現同步(synchronization),避免了競態條件(race condition)的產生,而競態條件產生在當多個線程試圖訪問相同的資源的時候,這會引發沒法預計的後果。有了同步,你就保護了資源不會被多個線程同時訪問。網絡

關於併發你都須要知道些什麼?

在本篇教程中,我會解釋爲理解併發你所須要瞭解的一切,消除你對它的全部恐懼心理。首先我推薦你去看一下 blocks(即 Swift 中的 closures),它們在併發中會被大量使用。以後咱們會聊一下 Dispatch QueuesNSOperationQueues。我會帶你瞭解每一個併發中的概念,概念之間的不一樣,以及如何使用它們。數據結構

第一部分:GCD(Grand Central Dispatch)

GCD 是在系統的Unix層級中用於管理併發代碼並異步執行操做時最經常使用的 API。GCD 提供而且管理任務的隊列(queues of tasks)。先來看看什麼是隊列。多線程

什麼是隊列

隊列是按照先進先出(FIFO)順序管理對象的數據結構。隊列相似於電影院的售票窗口前的長隊。電影票是按先到先得的順序賣出。長隊前面的人先買到票,晚來的人後買到票。計算機科學中的隊列概念和這個很像,由於第一個被加到隊列中的對象也是第一個要從隊列中被移除的。
Photo credit: FreeImages.com/Sigurd Decroos閉包

Dispatch Queues

Dispatch Queues 是一種可以輕鬆執行異步和併發任務的方式。它們是隊列,其中的任務是由你的 App 以 blocks(代碼塊)的形式提交。Dispatch Queues 有兩種:(1)串行隊列(serial queues),和(2)併發隊列(concurrent queues)。在講述二者的不一樣以前,你須要知道派給這兩種隊列的任務是在另外的線程中被執行,而不是在建立它們的那個線程中被執行。換句話說,你是在主線程中建立block並將其提交到 Dispatch Queues 中去。但全部這些任務(block)會在其餘的線程中運行,並不是主線程。併發

串行隊列

當你選擇建立一個串行隊列時,該隊列在某一時刻只能執行一個任務。該隊列中的全部任務都會彼此尊重,按序執行。然而你無需擔憂其餘隊列中的任務,意思是你依然能夠經過使用多個串行隊列來以併發的形式執行任務。例如你能夠建立兩個串行隊列,每個隊列某一時刻只能執行一個任務,可是仍是有最多兩個任務被併發執行。

用串行隊列來管理一個共享資源(shared resource)再合適不過。它提供的對共享資源的訪問確保是串行化的,從而防止競態條件的發生。想象一下有個售票小攤,還有一大堆人想買電影票,那小攤的售票員就是一個共享資源。若是這個售票員必須同時爲這堆人服務時就會特別混亂。爲避免這個狀況,買票的人會被要求去排隊(串行隊列),這樣售票員同一時刻就能夠只對一人服務。

再說一遍,這裏沒有說電影院每時刻只能爲一個顧客服務。若是電影院再開兩個售票點,就能夠同時服務三位客戶了。這也是爲何我說過即便你使用串行隊列還依然能並行執行多個任務的緣由。

使用串行隊列的優勢:

  1. 能確保對一個共享資源進行串行化的訪問,避免了競態條件;

  2. 任務的執行順序是可預知的;你向一個串行隊列提交任務時,它們被執行的順序與它們被提交的順序相同;

  3. 你能夠建立任意數量的串行隊列;

併發隊列

正如其名,併發隊列可讓你並行的執行多個任務。任務(block)按照它們被加入到隊列中的順序依次開始,可是它們都是併發的被執行,並不須要彼此等待纔開始。併發隊列能保證任務按同一順序開始,但你不能知道執行的順序、執行的時間以及在某一時刻正在被執行任務的數量。

好比你向一個併發隊列提交了三個任務(任務#1,#2和#3)。任務被併發執行,按照加入隊列的順序依次開始。然而任務的執行時間和結束時間都不相同。即便任務#2和#3可能會遲一些開始,它們可能都會先於任務#1結束。對任務的執行是由系統自己決定。

使用隊列

已經解釋了串行隊列和併發隊列,如今來看看如何使用它們。系統會缺省爲每一個應用提供一個串行隊列和四個併發隊列。其中 main dispatch queue(主派發隊列)是全局可用的串行隊列,在應用的主線程中執行任務。這個隊列被用來更新 App 的 UI,執行全部與更新 UIViews 相關的任務。該隊列中同一時刻只執行一個任務,這就是爲何當你在主隊列中運行一個繁重的任務時UI會被阻塞的緣由。

除主隊列以外,系統還提供了4個併發隊列。咱們管它們叫 Global Dispatch queues(全局派發隊列)。這些隊列對整個應用來講是全局可用的,彼此只有優先級高低的區別。要使用其中一個全局併發隊列的話,你得使用 dispatch_get_global_queue 函數得到一個你想要的隊列的引用,該函數的第一個參數取以下值:

  • DISPATCH_QUEUE_PRIORITY_HIGH

  • DISPATCH_QUEUE_PRIORITY_DEFAULT

  • DISPATCH_QUEUE_PRIORITY_LOW

  • DISPATCH_QUEUE_PRIORITY_BACKGROUND

這些隊列類型表明着執行優先級。帶有 HIGH 的隊列有最高優先級,BACKGROUND 則是最低的優先級。這樣你就能基於任務的優先級來決定要用哪個隊列。還要注意這些隊列也被 Apple 的 API 所使用,因此這些隊列中並不僅有你本身的任務。

最後,你能夠建立任何數量的串行或併發隊列。使用併發隊列的狀況下,即便你能夠本身建立,我仍是強烈建議你使用上面那四個全局隊列。

GCD 小抄

如今你應該有了一個對 Dispatch Queues 的基本瞭解。我會給你一個簡單的小抄作參考。裏面很簡單,包含了對 GCD 你須要瞭解的全部信息。

還不錯吧?如今咱們來研究一個簡單的示範,看看如何使用 Dispatch Queues。我會告訴你如何使用 Dispatch Queues 來優化 App 的性能,讓它有更快的響應速度。

示例項目

咱們的初始項目很簡單,它展現4個 image views,每一個 image view 顯示一張來自遠端站點的圖片。圖片的請求是在主線程中完成。爲了給你展現這麼作對UI響應會有何影響,我還在圖片下面加了一個簡單的 slider。下載並運行這個初始項目。點擊 Start 按鈕開始圖片的下載,而後在圖片下載的過程當中拖動 slider,你會發現根本就拖不動。


你點了 Start 按鈕以後,圖片就會在主線程中開始下載。顯然這種方式糟糕至極,讓 UI 沒法響應。不幸的是時至今日還有很對的 App 依舊在主線程中執行繁重的裝載任務。如今咱們使用 Dispatch Queues 來解決這個問題。

首先咱們使用併發隊列的解決方案,隨後再使用串行隊列的解決方案。

使用 Concurrent Dispatch Queues

如今回到 Xcode 項目的 ViewController.swift 文件中。若是你細看一下代碼,就會看到點擊事件的方法 didClickOnStart。這個方法負責處理圖片的下載。咱們如今是這樣來完成該任務的:

@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 都被看做是一個任務,而全部的任務都在主隊列中被執行。如今咱們來得到一個全局併發隊列的引用,該隊列是默認優先級的那個。

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 得到到默認併發隊列的引用,而後在 block 中提交一個任務,下載第一張圖片。當圖片下載完成後,咱們再向主隊列提交另一個任務,這個任務用拿下載好了的圖片去更新 image view。換句話說,咱們就是將圖片下載任務放到了後臺線程中執行,而 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
        })
    }
    
}

你向默認隊列以併發任務的形式提交了四個圖片的下載任務。如今建立項目而後運行 App ,運行起來應該更快了(若是你收到任何錯誤告警,在把你的代碼和上面的比較一下)。注意到下載圖片的過程當中你應該能夠拖動那個 slider,沒有任何延遲。

使用 Serial Dispatch Queues

另外一種解決延遲問題的方法是使用串行隊列。如今仍是回到 ViewController.swift 中的 didClickOnStart() 方法。這回咱們用一個串行隊列來下載圖片。使用串行隊列時你必定要留意你所引用的究竟是哪個串行隊列。每個 App 都有一個默認的串行隊列,實際上它也是UI任務相關的主隊列。因此切記用串行隊列的時候,你必定要建立一個新的,不然就會在 App 嘗試執行更新UI相關任務的同時又執行你的任務。這就會產生錯誤,引發延時,毀掉用戶體驗。你可使用 dispatch_queue_create 函數建立一個新的隊列,而後將全部任務按相同方式提交給它,和咱們以前作的同樣。更改以後,代碼以下:

@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
        })
    }
    
}

正如咱們所見,與並行隊列解決方案惟一的不一樣就是須要建立一個串行隊列。再次點擊 build 而後運行 App ,你又會看見圖片在後臺進行下載,因此能夠和UI進行交互。

可是你會注意到兩點:

  1. 與使用併發隊列的狀況相比,下載圖片的時間有些長。緣由是咱們在同一時刻只下載一張圖片。每一個任務必須等到前一個任務執行完成後纔會被執行。

  2. 圖片的下載是按照 image1,image2,image3,和 image4 的順序。由於使用的是每次只能執行一個任務的串行隊列。

第二部分:Operation Queues

GCD 是一個底層的 C API,能讓開發者並行執行任務。與之相對比,Operation queues 是對隊列模型的高層級抽象,並且是基於GCD建立的。這意味着你能夠像GCD那樣執行併發任務,只不過是以一種面性對象的風格。簡而言之,Operation Queues 讓開發者的「來福」更進一步。

與 GCD 不一樣的是,Operation Queues 不遵循先進先出的順序。如下是 Operation Queues 和 Dispatch Queues 的不一樣:

  1. 不遵循 FIFO(先進先出):在 Operation Queues 中,你能夠設置 operation(操做)的執行優先級,而且能夠在 operation 之間添加依賴,這意味着你能夠定義某些 operation,使得它們能夠在另一些 operation 執行完畢以後再被執行。這就是爲何它們不遵循先進先出的順序。

  2. 默認狀況下 Operation Queues 是併發執行:雖然你不能將其改爲串行隊列,但仍是有一種方法,經過在 operation 之間添加相依性來讓 Operation Queues 中的任務按序執行。

  3. Operation Queues 是 NSOperationQueue 類的實例,任務被封裝在 NSOperation 的實例中。

NSOperation

任務是以 NSOperation 實例的形式被提交到 Operation Queues 中去的。以前說過 GCD 中任務是以 block 的形式被提交。在這裏也是相似,只不過是須要被綁到 NSOperation 實例中。你能夠簡單的將 NSOperation 看做是一套工做任務的總體。

NSOperation 是一個抽象的類,不能夠被直接拿來用,因此你只能使用 NSOperation 的子類。在 iOS 的 SDK 中有兩個 NSOperation 的具體子類。這兩個類能夠直接用,可是你也能夠用 NSOperation 的子類,建立你本身的類來完成特定 operation。這兩個能夠直接使用的類是:

  1. NSBlockOperation - 用這個類來初始化包含一個或多個 blocks 的 operation。該 operation 自己可包含的 block 超過一個,當全部的block 執行完畢後這個 operation 就被視爲已完成。

  2. NSInvocationOperation  - 用這個類來初始化一個 operation,能用來調用某指定對象的選擇器(selector)。

那麼 NSOperation 的優點在哪裏?

  1. 首先它能夠經過 NSOperation 類的 addDependency(op: NSOperation)方法得到對相依性的支持。若是你有這樣的需求:即某 operation 的啓動需取決於另外一個 operation 的執行,那麼就得用 NSOperation

  2. 其次,你可將 queuePriority 屬性設爲如下值來改變執行優先級:

    public enum NSOperationQueuePriority : Int {

    case VeryLow
       case Low
       case Normal
       case High
       case VeryHigh

    }
    擁有最高優先級的 operation 會被第一個執行。

  3. 你能夠取消掉某特定隊列中的某個 operation,或者是取消隊列中全部的 operation。
    經過調用 NSOperation 類的 cancel() 方法來實現對 operation 的取消。你取消任何 operation 的時候,會是下面三種場景之一:

    • 你的 operation 已經完成了,這種狀況下 cancel 方法沒有任何效果。

    • 你的 operation 正在被執行的過程當中,這種狀況下系統不會強制中止你的 operation 代碼,而是將 cancelled 屬性置爲 true。

    • 你的 operation 還在隊列中等待被執行,這種狀況下你的 operation 就不會被執行。

  4. NSOperation 有3個有用的布爾型屬性:finishedcancelledreadyfinished 在 operation 執行完畢後被置爲 true。cancelled 在 operation 被取消後被置爲 true。ready 在 operation 即將被執行時被置爲 true。

  5. 全部的 NSOperation 在任務被完成後均可以選擇去設置一段 completion block。NSOperationfinished 屬性變爲 true 後這段 block 就會被執行。

如今來重寫一下咱們的示例項目,此次使用 NSOperationQueues。首先在 ViewController 類中聲明以下變量:

var queue = NSOperationQueue()

而後將 didClickOnStart 方法中的代碼替換成下面的,再看看在 NSOperationQueue 中怎樣去執行 operation:

@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 方法來建立一個新的、帶有某給定 block(或者它在 Swift 中的名字:閉包)的 operation。很簡單,對吧?爲在主隊列中完成某個任務,與使用 GCD 時調用 dispatch_async() 不一樣,咱們用 NSOperationQueue(NSOperationQueue.mainQueue())也能夠達到相同結果,將你想要在主隊列中執行的 operation 提交過去。

能夠運行一下這個 App 作個快速測試。若是代碼輸入正確的話, App 應該可以在後臺下載圖片,不會阻塞UI。

以前的例子中咱們使用了 addOperationWithBlock 方法把 operation 添加到隊列中。再讓咱們來看看如何使用 NSBlockOperation:在達到相同的效果的同時,還會給咱們提供更多的功能和選項,好比設置 completion handler。改後的 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)
}

對每一個 operation,咱們都爲其建立了一個新的 NSBlockOperation 實例並將任務封裝在一個 block 中。而使用了 NSBlockOperation,你還能夠設置 completion handler。當 operation 完成後,completion handler 就會被調用。將示例運行一下,就會在控制檯看見這樣的輸出:

Operation 1 completed
Operation 3 completed
Operation 2 completed
Operation 4 completed

取消 operation

以前提過,NSBlockOperation 可以讓你管理 operation。那麼如今來看看如何取消一個 operation。首先給 navigation bar 加一個 bar button item,將其命名爲 Cancel。爲展現取消 operation,咱們在 Operation #2 和 Operation #1 之間添加一個相依性,Operation #3 和 Operation #2 之間添加另外一個相依性。也就是說 Operation #2 會在 Operation #1 完成後開始執行,而 Operation #3 會在 Operation #2 完成後執行。Operation #4 並無相依性,它會被併發執行。要取消 operation 的話,你只需調用 NSOperationQueue 的 cancelAllOperations() 方法。在ViewController 類中插入下面的方法:

@IBAction func didClickOnCancel(sender: AnyObject) {
        self.queue.cancelAllOperations()
}

記住須要把你在 navigation bar 上添加的 Cancel 按鈕與 didClickOnCancel 方法關聯起來。你能夠這麼作:返回 Main.storyboard 文件,打開Connections Inspector,這裏你會在Received Actions區域中看見unlink didSelectCancel()。點擊 + 並將其從空圓圈拖拽到 Cancel bar button 上。而後在 didClickOnStart 方法中添加相依性:

operation2.addDependency(operation1)
operation3.addDependency(operation2)

接下來把 operation #1 的 completion block 改一下,讓它在控制檯打印出 cancel 的狀態:

operation1.completionBlock = {
    print("Operation 1 completed, cancelled:\(operation1.cancelled) ")
}

你能夠本身改一下 operation #2,#3 和 #4 的打印語句,這樣能夠更好的理解這一過程。而後建立並運行項目。你點擊了 Start 按鈕以後,再按 Cancel 按鈕,這就會在 operation #1 執行完畢後取消全部的 operation。下面告訴了咱們都發生了些什麼:

  • 因爲 operation #1 已經開始執行,取消對它沒有任何效果。這就是爲何 cancelled 會被記錄成 false,而且 App 仍是會顯示第一張圖片。

  • 若是你點擊 Cancel 按鈕足夠快的話,operation #2 會被取消。對 cancelAllOperations() 的調用會中止對該 operation 的執行,因此第二張圖片沒有被下載。

  • operation #3 已經排在隊列中,等待 operation #2 的完成。由於 operation #3 是否開始取決於 operation #2 的完成與否,而 operation #2 已經被取消,operation #3 就不會被執行,從隊列中被當即踢出了。

  • 沒有對 operation #4 作任何相依性的設置,因此它被併發的執行了,下載了第四張圖片。

接下來看什麼?

本篇教程中我爲你講解了 iOS 中併發的概念,以及你在 iOS 中該如何去使用它。我給了你一個還不錯的併發入門簡介,解釋了 GCD,並示範了怎樣去建立串行和併發隊列。除此以外,咱們還看了一下 NSOperationQueues。你如今應該對 GCD 和 NSOperationQueues 之間的不一樣有所瞭解了。

若是想進一步瞭解 iOS 的併發,建議你去看一下 Apple 的併發指南

你能夠從 iOS Concurrency repository on Github 這裏找到此教程提到的全套源代碼以做參考。

隨便問任何問題,我真心喜歡你的評論。

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg

相關文章
相關標籤/搜索