DispatchQueue 的簡單應用
本篇內容以非常初階的方式介紹 DispatchQueue,
同時會說明 iOS 體系裡最流行的多緒處理的技術 GCD 和一些應用情境和範例,
後來也有用在公司培訓初次接觸 iOS 工程師的一段課程裡。
不過本篇的主題不是最新 Swift 5.5 的 async/await,將會著重在 GCD 的 DispatchQueue
提及 DispatchQueue 前,先需要知道多執行緒的應用程式如何運作的?
- Program:
- 應用程式本身
- Process:
- 開啟應用程式後的實體
- 每個 Process 都是獨立的,無法自由讀取其他 Process 的資源
- 像是裝有 Thread 的容器
- Thread:
- 每個 Process 會有管理多個 Thread
- 同一個 Process 底下的 Thread 就可以存取相同資源 (例如記憶體變數等等)
- 多執行緒處理需要非常細微的操作,容易發生互搶資源,死結 DeadLock 的問題
- Core:
- 就是硬體上的核心,通常是多核心處理
- 一個 Core 通常同一時間只能處理一個 Thread (還是有些例外像是 Hyper-Threading)
多執行緒程式碼可以同時間處理多項任務,然而處理多項任務就是需要操作 Thread
iOS 的多緒開發方式
第一種:Threading Programming Guide
自行建立 NSThread 並且需要自行管理 Autorelease Pool
需要定義 Atomic 變數或是自行管理 NSLock 來處理同步問題第二種:Concurrency Programming Guide
名為 Grand Central Dispatch ( GCD )
消除建立與管理 Thread 所需程式碼,僅需定義任務,讓系統自行管理排程
可以高效穩定地避免同步和 DeadLock 問題第三種:Swift Concurrency
最新的 Swift 5.5 提供了更直觀、更安全的寫法
包含 async/await、Actor 等等好用的語法
Grand Central Dispatch ( GCD )
目前提供了三種方法處理多執行緒
- 第一種:Dispatch Queues
- 利用 DispatchQueue,可以輕鬆達到 asynchronously 異步和 concurrently 並發的方式,處理你的工作
- 依照先出原則管理
- 第二種:Dispatch Sources
- 可以處理 Dispatch 的底層系統事件的通知
- 第三種:Operation Queues
- 預設並行方式,不依照先進先出原則,根據優先順序決定
DispatchQueue 的建立與發動
建立方式有兩種
- Serial:串行的方式執行工作,一次只能執行一項任務
- Concurrent:並發的方式執行工作,可以同時執行多項任務
1 | // 指定名稱可用於取得相同 Queue 或是避免衝突 |
也可以執行呼叫出系統預設的 DispatchQueue
- DispatchQueue.main
- 屬於 Serial 方式
- 全域可以操作的主要 Queue
- UI相關的操作必須要在這裡操作
- DispatchQueue.global
- 屬於 Concurrent 方式
- 適合在背景處理大量計算
1 | let mainQueue = DispatchQueue.main //<——- 為 serial |
發動方式有兩種
- Synchronously 同步發動:發動工作後,必須執行完成工作,才能往下執行
queue.sync { 定義你要做的工作 }
- Asynchronously 異步發動:發動工作後,直接執行後續動作
queue.async { 定義你要做的工作 }
DispatchQueue 組合運用範例
關鍵判斷方式:
- 決定 Closure 裡面可不可以執行看 Serial vs Concurrent
- 決定後續是否直接執行看 Sync vs Async
1 | let serialQueue = DispatchQueue(label: "serialQueue") |
由於執行 sync 所以 Closure1 內要先執行完成,才可以往下走
start > Closure1 > Closure2 > end
1 | start |
1 | let serialQueue = DispatchQueue(label: "serialQueue") |
由於執行 async 所以可以連續執行到下一個 async 和最後一個 print end,
但因為是 serialQueue,所以導致先執行完第一個 Closure1 才能執行 Closure2
1 | start |
1 | let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent) |
由於執行 sync 所以 Closure1 內要先執行完成,才可以往下走
所以就算是 concurrentQueue 也一樣
1 | start |
1 | let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent) |
由於執行 async 所以可以連續執行到下一個 async 和最後一個 print end,
並且因為是 concurrentQueue 所以 Closure1 和 Closure2 是同時進行的。
1 | start |
為什麼 async 的發動方式可以連續執行後續的程式碼呢?
原因是因為 async 會特別建立新的 thread 來處理 async 裡的工作,
因此當前的執行緒可以繼續執行後續的程式碼。
知道上面那些組合,那他的應用情境到底是哪裡?
DispatchQueue 應用情境
情境一:處理大量資料
通常不希望處理大量資料時會卡住使用者畫面,所以需要在背景執行工作。
可以使用 DispatchQueue.global().async
,
但記得要回 main thread 才能更新畫面。
1 | func handleBigData(){ |
情境二:呼叫API
最常使用的 API 三方 Alamofire 幫我們處理好了,
呼叫 API 前三方內部會用 async 讓畫面不會卡,
同時預設回應是回 main queue (也可以自行設定 queue)。
1 | func handleCallApi(){ |
情境三:多道API並行呼叫,全部完成時要做統一處理
多道API呼叫後的結果,可能需要最後做一個統整的處理,最後才能更新畫面。
可以利用 group(enter+leave+notify)。
1 | func handleMergeApi(){ |
情境四:一道一道呼叫API
由於 Alamofire 預設是 async 並行發送,
要做一點處理才能一道API完成後,再呼叫下一道API。
可以利用 group(enter+leave+wait),但切記 wait 不可以在 main thread 上執行不然會卡死,
所以下面範例才使用 DispatchQueue.global().async
。
1 | func handlePipeApi(){ |
DispatchQueue 應用情境 Demo
希望這篇文章有幫助到您的開發之路!如果能給我一些按讚支持,我會非常感謝您的鼓勵!祝壞蟲遠離您!