【iOS】Swift4.0 GCD的使用筆記
前言
在Swift4.0版本中GCD的常用方法還是有比較大的改動,這裡做個簡單的整理彙總。
GCD的佇列
佇列是一種遵循先進先出(FIFO)原則的資料結構,是一種特殊的線性表。
主佇列 | 全域性佇列 | 序列佇列 | 並行佇列 | |
---|---|---|---|---|
同步 | X | 並行同步 |
|
並行同步 |
非同步 | 序列非同步 | 並行非同步 | 序列非同步 | 並行非同步 |
X 表示禁止這麼使用,—— 表示不建議這麼使用。
1. 主佇列
主佇列預設是序列的,另外主佇列不能結合同步函式( sync )使用,會造成執行緒死鎖。
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let mainQueue = DispatchQueue.main mainQueue.sync { print("造成當前執行緒:\(Thread.current)死鎖") } }
同時主佇列中不應該新增耗時的任務,因為系統的UI相關事務都是在主執行緒佇列中完成的,大量大耗時操作可能會造成卡頓,應該避免。
主佇列最常用的方法是當子執行緒需要通知主執行緒做一些UI上面的操作時,結合子執行緒使用:
let queue = DispatchQueue(label: "com.roki.thread") queue.async { // 大量耗時操作 print("大量耗時操作執行緒:\(Thread.current)") Thread.sleep(forTimeInterval: 2) DispatchQueue.main.async { //回到主執行緒操作UI print("回到主執行緒:\(Thread.current)") } }

DF993F5D-6A1D-4869-A7E8-5E9D871915D8.png
2. 全域性佇列
全域性佇列是由系統建立的,預設是並行的。全域性佇列具體執行在哪一個執行緒,是由系統維護一個執行緒池,然後挑選其中的一至多條執行緒來使用。哪條執行緒會被使用是未知的,是由系統根據當前的併發任務,處理器的負載等情況來決定的。
- 全域性併發同步佇列
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. for i in 1...10 { DispatchQueue.global().sync { //全域性併發同步 Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行\(i)號任務") } } }

9C11F7B3-7602-4E3B-94A2-0255DFC77077.png
從終端輸出我們可以知道任務被順序執行了,這是因為雖然當前是一個併發佇列,但是是同步執行的。同步操作會使得在前一個任務完成後才去執行下一個任務。 同步與非同步的區別 還在於它不會建立新的執行緒,而是直接在當前執行緒中執行了相關的任務,當前執行緒是主執行緒。同步會阻塞當前執行緒。
- 全域性併發非同步佇列
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. for i in 1...10 { DispatchQueue.global().async { //全域性併發非同步 Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行\(i)號任務") } } }

7D63F60B-3DC4-4071-854E-CCB3E36019E4.png
從終端輸出我們可以知道任務被隨機執行了,而且被分配在多個子執行緒中執行的,這符合併發的本質。另外需要注意的是全域性併發非同步佇列,系統在挑選來執行任務的執行緒的時候,會挑選除了主執行緒之外的其他執行緒。
3. 自定義佇列
除了上述佇列之外,我們還可以使用 DispatchQueue 建立自定義的佇列。
let queue = DispatchQueue(label: "com.roki.thread")
需要注意的是上述建立自定義佇列的方式,預設建立的是序列佇列。
還有一種建立自定義佇列的方法是:
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: .workItem, target: nil)
引數說明:
-
label 表示佇列標籤
-
qos 表示設定佇列的優先順序
- .userInteractive 需要使用者互動的,優先順序最高,和主執行緒一樣
- .userInitiated 即將需要,使用者期望優先順序,優先順序高比較高
- .default 預設優先順序
- .utility 需要執行一段時間後,再通知使用者,優先順序低
- *.background 後臺執行的,優先順序比較低
- *.unspecified 不指定優先順序,最低
-
attributes 表示佇列型別,預設為序列佇列,設定為 .concurrent 表示並行佇列。iOS 10.0之後 attributes 新增 .initiallyInactive 屬性表示當前佇列是不活躍的,它需要呼叫 DispatchQueue 的 activate 方法來執行任務。
-
autoreleaseFrequency 表示自動釋放頻率,設定自動釋放機制。
- .inherit 表示不確定,之前預設的行為也是現在的預設值
- .workItem 表示為每個執行的專案建立和排除自動釋放池, 專案完成時清理臨時物件
- .never 表示GCD不為您管理自動釋放池
-
同步序列佇列
其實同步序列佇列,沒什麼意思的,不管是同步操作還是序列操作都會導致任務被一個一個的執行。這個操作尤其是在主執行緒執行的時候需要注意,避免造成執行緒的卡頓。
let queue = DispatchQueue(label: "com.custom.thread") queue.sync { //同步序列佇列 }
-
非同步序列佇列
因為是序列佇列,即使是非同步執行的,任務也是按照順序依次執行的,但是在子執行緒中執行的。
let queue = DispatchQueue(label: "com.custom.thread") queue.async { //非同步序列佇列 }

9D774A76-4F71-49AE-903A-57741B89D1DE.png
根據iOS10.0 之後attributes新增的 .initiallyInactive 屬性,我們可以建立不活躍佇列。
- 同步序列不活躍佇列
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.initiallyInactive, autoreleaseFrequency: .workItem, target: nil) queue.sync { //同步序列不活躍佇列 } queue.activate()
- 非同步並行不活躍佇列
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: [.initiallyInactive, .concurrent], autoreleaseFrequency: .workItem, target: nil) queue.async { //非同步並行不活躍佇列 } queue.activate()
-
同步並行佇列
只要涉及到同步的,都不會開啟新執行緒,會在當前執行緒執行任務,同時任務只能依次執行。
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) for i in 1...10 { queue.sync { //併發同步佇列 Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行\(i)號任務") } }

BC7AAE0B-C3FA-4AEB-9593-72E02D4A105F.png
-
非同步並行佇列
非同步並行佇列就會在多個執行緒中,隨機執行任務。
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) for i in 1...10 { queue.async { //併發非同步 Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行\(i)號任務") } }

EA277C08-A7FF-49B5-B26B-73521A51C5CF.png
4. 任務組( DispatchGroup )
如果我們想監聽多個任務的執行情況,那麼我們需要將任務(非同步、同步、序列、並行)都新增到任務組中,然後通過 DispatchGroup 的 notify 函式就可以監聽是否組內任務都已經完成。
let group = DispatchGroup() let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) for i in 1...10 { queue.async(group: group) { //併發非同步 Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行\(i)號任務") } } group.notify(queue: DispatchQueue.main) { // 通知主執行緒,子執行緒操作已完成 print("所有任務都已經完成") }

05A6287E-81AC-49A0-A277-D53994DB8E0A.png
5. 任務物件( DispatchWorkItem )
在Swift4.0 中使用 DispatchWorkItem 代替原來OC中的 dispatch_block_t 。 在 DispatchQueue 執行操作,除了直接傳了一個() -> Void 型別的閉包外,還可以傳入一個 DispatchWorkItem 任務物件。 DispatchWorkItem 的初始化方法可以配置 Qos 和 DispatchWorkItemFlags ,但是這兩個引數都有預設引數,所以也可以只傳入一個閉包。
DispatchWorkItemFlags 列舉中 assignCurrentContext 表示 QoS 根據建立時的 context 決定。 值得一提的是 DispatchWorkItem 也有 wait 方法,使用方式和 DispatchGroup 一樣。呼叫會等待這個 workItem 執行完。
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) let workItem = DispatchWorkItem { Thread.sleep(forTimeInterval: 2) print("執行緒\(Thread.current)正在執行任務") } queue.async(execute: workItem) print("before waiting") workItem.wait() print("after waiting")

A25890FA-C5B3-4000-94D1-D00044663A1A.png