Swift多執行緒:使用GCD實現非同步下載圖片

非典型技術宅發表於2018-02-15

GCD屬於系統及的執行緒管理,功能很強大,比上兩次我們們分享的Operation要強大。有很多老前輩們已經創造了非常非常多的資料介紹GCD,因為大家都是把GCD放在了多執行緒內容分享的最開始,所以導致好多好多理論知識都被放在了GCD部分。

哈哈~幸好非典型技術宅英明神武的錯峰出行,把一些基礎概念放在了上兩篇文章裡面。極大的減輕了這篇文章的閱讀負擔。

既然前人都早了辣麼多輪子,俺就不想再多介紹一些基礎理論知識了。反正碼再多的字,只會讓大家立刻馬上關掉這篇文章。而且上一篇關於Operation的閱讀量就明顯不高,看來大家不喜歡看啊。。。

那就容我偷偷懶嘛~重點還是分享一些程式碼吧。

不是說理論知識不重要啊,面試全都問這個。而且理論知識直接影響到對技術的理解深度,決定能在這條路上走多遠。是會成為某個領域的大牛,還是隻是簡單的應用者。

1. GCD基礎知識

納尼?不是說不說基本概念了嗎?easy~easy~easy~~~只介紹一些那些最最重要的,不瞭解就會影響到閱讀這篇文章的內容啦。

其實GCD和Operation很多地方驚人的相似。廢話,都是多執行緒,底層都差不多,能不相似嘛!

GCD使用只需要兩步:

  • STEP ONE:建立任務。
  • STEP TWO:把任務放進佇列裡。

。。。。。。~!@#¥%……&*¥%#@!~@#¥%…… 把大象放進冰箱裡需要幾步?!兩步!開啟冰箱門,把大象放進去!宅胖,現在很想抽死你啊!

確實真的就是這樣的。內心OS:這只是為了騙你入門,讓你覺得好簡單

1.1 任務的分類

上面說了任務,任務只有兩種方式:同步、非同步。

  • 非同步(asynchronous)具備開啟新執行緒的能力,也具備跳過當前程式碼繼續往下執行的能力。
  • 同步(synchronous)不具備開啟新執行緒的能力,也不具備跳過當前程式碼繼續往下執行的能力。
名稱 開啟新執行緒的能力 跳過當前程式碼繼續往下執行的能力
非同步
同步 NULL NULL

換句話簡單的說,非同步任務就是可以同時開啟多個跑道,同時跑好多輛車。同步就是隻有一條車道,堵死也飛不過去,只能乖乖的等著,一輛接一輛。

任務放入到佇列裡面,會遵循first in first out原則。舉個噁心的例子,就像是拉屎,先吃先拉,後吃後拉。 哈哈~看了這個比方,別打死我~

image.png

1.2 佇列的分類

佇列吶,也只有兩種:序列佇列(Serial Dispatch Queue)、併發佇列(Concurrent Dispatch Queue)。

  • 序列佇列(Serial Dispatch Queue): 讓任務一個接著一個有序的執行,一個任務執行完畢後,再執行下一個任務。

  • 併發佇列(Concurrent Dispatch Queue) 可以讓多個任務同時執行,自動開啟多個執行緒同時執行多個任務。

咦?有點暈,怎麼感覺跟剛才的任務分類一樣吶?沒錯!就是這樣的。

下面為了讓大家不要暈菜,我們把佇列這個中文名字統一都叫做Queue,這樣就和OperationQueue對應起來了,就不會那麼暈了。

Serial QueueConcurrent Queue各自都有一個特殊的Queue

主佇列(main queue):是Serial Queue中特殊的一種。只能在主執行緒中進行,並且主佇列裡面的任務,只有當主執行緒空閒的時候才能被執行。用來重新整理UI使用。

全域性佇列(global queue):是Concurrent Queue中特殊的一種。用來執行耗時操作。

同時,GCD裡面還可以自定義Queue。

1.3 排列組合開始

最開始的時候,我們們是不是說了,使用GCD就只有兩步:建立任務,把任務放進Queue裡。

任務有兩種:同步、非同步。Queue加上兩種特殊的(不包括自定義的)一共有四種。來吧,開始排列組合吧。有八種吧。

名稱 能夠開啟新執行緒 能夠跳過當前程式碼繼續進行
非同步
同步 / /
Queue 序列佇列Serial 並行佇列concurrent 主佇列main 全域性佇列global
能夠多個任務同時執行 / /

哈哈哈O(∩_∩)O哈哈~????


徹底暈菜?

oooO ↘┏━┓ ↙ Oooo ( 踩)→┃你┃ ←(死 ) \ ( →┃√┃ ← ) /   _)↗┗━┛ ↖(_/


來吧,直接告訴你結論吧。裡面有幾個特例。

序列佇列Serial Queue 並行佇列concurrent Queue 主佇列main Queue 全域性佇列global Queue
非同步 新執行緒、序列執行 新執行緒並行執行 無新執行緒、序列執行 新執行緒並行執行
同步 無新執行緒、序列執行 無新執行緒、序列執行 沒事會鎖死 無新執行緒、序列執行

看上面這個表,所以如果想要同時做事情,當然不能選同步任務啦。因為它完全沒能力!搞不好還會造成鎖死。

要想同時做事情,就選concurrent Queue + 非同步,或者global Queue + 非同步。 不過人家global Queue本來就是concurrent Queue特殊的一種。

如果有多工,工作中最最省事兒常用的就是global Queue + 非同步。單任務、重新整理UI就用main Queue + 非同步。

上面都沒心思看也沒關係。工作中,如果有多工,首選global Queue + 非同步。單任務、重新整理UI就用main Queue + 非同步。

2. GCD的基礎應用

我滴媽媽~經過上面的分析,最後,最基礎的使用就兩種了。 多工:global Queue + 非同步。 單任務、重新整理UI就用main Queue + 非同步。

說實話,我也是第一次這麼大膽的簡化。會不會被大神們拍死?坐等~~~~

2.1 global Queue + 非同步任務

/// global Queue + 非同步任務
@IBAction func globalAsyn(_ sender: Any) {
    //建立一個全域性佇列。
    //get a global queue
    let globalQueue = DispatchQueue.global()
    for i in 0...10 {
        
        //使用全域性佇列,開啟非同步任務。
        //use the global queue , run in asynchronous
        globalQueue.async {
            print("I am No.\(i), current thread name is:\(Thread.current)")
        }
    }
}
複製程式碼

image.png

我們看一下執行的結果,亂序列印的,並且沒有在主執行緒中。這證明了確實是多個任務沒有按照順序執行。

2.2 main Queue + 非同步任務

/// main Queue + 非同步任務
@IBAction func mainAsyn(_ sender: Any) {
    //建立一個主佇列
    //get a main queue
    let mainQueue = DispatchQueue.main
    
    for i in 0...10 {
        
        //使用主佇列,開啟非同步任務
        //use the main queue, run in asynchronous
        mainQueue.async {
            print("I am No.\(i), current thread name is:\(Thread.current)")
            
        }
    }
}
複製程式碼

image.png
我們看一下執行的結果,確實是順序列印的。並且都執行在了主執行緒中。

2.3 小實踐:實現非同步下載圖片

需求:非同步下載一張圖片,下載完成後顯示在UI介面

實現後的效果圖:

GCD.gif

思路:

  1. 在當前UI動作之外,開啟一個global Queue+非同步,用來下載圖片。因為過程可能很耗時。
  2. 等下載完成後,開啟一個main Queue+非同步,把下載的圖片賦值,重新整理UI。

這個小Demo其實也實現了執行緒間通訊。

@IBAction func asynDownloadImage(_ sender: Any) {
    let imageVC = ImageVC()
    DispatchQueue.global().async {
        
        if let url = URL.init(string: "https://placebeard.it/355/140") {
            do {
                let imageData = try Data(contentsOf: url)
                let image = UIImage(data: imageData)
                
                
               //因為宅胖家網路很好,為了模擬網路很耗時,就用了延時載入。
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(2), execute: {
                    imageVC.imageView.image = image
                    imageVC.imageView .sizeToFit()
                })
                
            } catch {
                print(error)
            }
        }
        
    }
    navigationController?.pushViewController(imageVC, animated: true)

}
複製程式碼

3. GCD的服務質量(優先順序)

DispatchQoS.QoSClass是在Swift中封裝的關於描述服務質量的類。

這個在Operation裡面也見到過,級別越高,就會給分配的資源越多。但是並不是嚴格按照級別的高低來執行的

image.png

這是一個列舉值:

public enum QoSClass {

    case background  //後臺默默執行,The background quality of service class.
    case utility  //通用的,The utility quality of service class.

    case `default` //預設值,The default quality of service class.


    case userInitiated  //使用者發起的,The user-initiated quality of service class.


    case userInteractive //用來執行使用者互動,The user-interactive quality of service class.


    case unspecified //沒啥重要事情,The absence of a quality of service class.


    public init?(rawValue: qos_class_t)

    public var rawValue: qos_class_t { get }
}
複製程式碼

看到上面的列舉值,也大概能猜出來優先順序的高低了。和介面相關的、使用者的肯定是高的,後臺默默執行的肯定是低的。

從高到低的順序分別是:userInteractive -> userInitiated -> default -> utility -> background -> unspecified

最基本的基礎基本上就到這裡了。掂量了一下,還有排程組、訊號量、阻塞等等都還沒寫。這時候發現一篇寫完GCD基礎貌似不太現實,又不想一篇文章過長,那就拆開吧。下次再說。

最後,所有的程式碼都放在這裡了:gitHub 下載後給顆Star吧~ 麼麼噠~(~o ̄3 ̄)~ 愛你們~

祝各位新春快樂~!~!

相關文章