【iOS】Swift4.0 GCD的使用筆記


https://www.jianshu.com/p/47e45367e524

前言

在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)

iOS10.0 之后上述API更新為:

let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: .workItem, target: nil)

參數說明:

  1. label 表示隊列標簽

  2. qos 表示設置隊列的優先級

    • .userInteractive 需要用戶交互的,優先級最高,和主線程一樣
    • .userInitiated 即將需要,用戶期望優先級,優先級高比較高
    • .default 默認優先級
    • .utility 需要執行一段時間后,再通知用戶,優先級低
    • *.background 后台執行的,優先級比較低
    • *.unspecified 不指定優先級,最低
  3. attributes 表示隊列類型,默認為串行隊列,設置為.concurrent表示並行隊列。iOS 10.0之后 attributes 新增.initiallyInactive屬性表示當前隊列是不活躍的,它需要調用DispatchQueueactivate方法來執行任務。

  4. 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)

如果我們想監聽多個任務的執行情況,那么我們需要將任務(異步、同步、串行、並行)都添加到任務組中,然后通過DispatchGroupnotify函數就可以監聽是否組內任務都已經完成。

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的初始化方法可以配置QosDispatchWorkItemFlags,但是這兩個參數都有默認參數,所以也可以只傳入一個閉包。
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
 
 

簡介

Grand Central Dispatch (GCD) 是Apple開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其他對稱多處理系統。

眾所周知,GCD, NSOperationQueue, NSThread, pthread是iOS中多線程的幾種處理方式,Swift3之前GCD仍是面向過程的寫法,所以需要封裝一層再使用。Swift3蘋果打成Dispatch這個module.你可以通過import進行導入再使用。Swift4,直接使用。

特性

GCD可用於多核的並行運算
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)

用法

  1. 異步執行回主線程寫法
DispatchQueue.global().async { print("async do something\(Thread.current)") DispatchQueue.main.async { print("come back to main thread\(Thread.current)") } } 
  1. QoS
    之前接觸過Quality of Service還是在VoIP,通過QoS來標注每個通信的priority,所以這邊其實是把
DISPATCH_QUEUE_PRIORITY_HIGHT
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

轉換成了

User Interactive 和用戶交互相關,比如動畫等等優先級最高。比如用戶連續拖拽的計算

User Initiated 需要立刻的結果,比如push一個ViewController之前的數據計算

Utility 可以執行很長時間,再通知用戶結果。比如下載一個文件,給用戶下載進度

Background 用戶不可見,比如在后台存儲大量數據

在GCD中,指定QoS有以下兩種方式
方式一,創建一個指定QoS的queue

let queue = DispatchQueue(label: "labelname", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit)

方式二,在提交block的時候,指定QoS

queue.async(group: nil, qos: .background, flags: .inheritQoS) { <#code#> } 

flags的參數有

public static let barrier: DispatchWorkItemFlags public static let detached: DispatchWorkItemFlags public static let assignCurrentContext: DispatchWorkItemFlags public static let noQoS: DispatchWorkItemFlags public static let inheritQoS: DispatchWorkItemFlags public static let enforceQoS: DispatchWorkItemFlags 

其中關於QoS的關系,可以通過flags參數設置。

  1. DispatchWorkItem
let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) { <#code#> } 
  1. after
let deadline = DispatchTime.now() + 5.0 DispatchQueue.global().asyncAfter(deadline: deadline) { <#code#> } 
  1. DispatchGroup

DispatchGroup用來管理一組任務的執行,然后監聽任務都完成的事件。比如,多個網絡請求同時發出去,等網絡請求都完成后reload UI。

let group = DispatchGroup() group.enter() self.sendHTTPRequest1(params:[String: Any]) { print("request complete") group.leave() } group.enter() self.sendHTTPRequest1(params:[String: Any]) { print("request complete") group.leave() } group.notify(queue: DispatchQueue.main) { print("all requests come back") } 
  1. Semaphore

Semaphore是保證線程安全的一種方式,而且繼OSSpinLock不再安全后,Semaphore似乎成為了最快的加鎖的方式。
如圖


 
1513585102364922.png
let semaphore = DispatchSemaphore(value: 2) let queue = DispatchQueue.global() queue.async { semaphore.wait() self.sendHTTPRequest1(params:[String: Any]) { print("request complete") semaphore.signal() } } queue.async { semaphore.wait() self.sendHTTPRequest2(params:[String: Any]) { print("request complete") semaphore.signal() } } queue.async { semaphore.wait() self.sendHTTPRequest3(params:[String: Any]) { print("request complete") semaphore.signal() } } 
  1. Barrier

GCD里的Barrier和NSOperationQueue的dependency比較接近,C任務開始之前需要A任務完成,或者A和B任務完成。

let queue = DispatchQueue(label: "foo", attributes: .concurrent) queue.async { self.sendHTTPRequest1(params:[String: Any]) { print("A") } } queue.async { self.sendHTTPRequest2(params:[String: Any]) { print("B") } } queue.async(flags: .barrier) { self.sendHTTPRequest3(params:[String: Any]) { print("C") } }
 
 
 
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM