Swift4 - GCD的使用
2018年03月30日 17:33:27 Longshihua 閱讀數:1165
版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/longshihua/article/details/79756676
從Swift3開始GCD的API就發生了很大的變化,更加簡潔,使用起來更方便。像我們經常開啟一個異步線程處理事情然后切回主線程刷新UI操作,這里就變的非常簡單了。
DispatchQueue.global().async {
// do async task
DispatchQueue.main.async {
// update UI
}
}
DispatchQueue
DispatchQueue字面意思就是派發列隊,主要是管理需要執行的任務,任務以閉包或者DispatchWorkItem的方式進行提交.列隊中的任務遵守FIFO原則。如果對於列隊不是很了解,可以看這里。 列隊可以是串行也可以是並發,串行列隊按順序執行,並發列隊會並發執行任務,但是我們並不知道具體任務的執行順序。
列隊的分類
系統列隊
主列隊
let mainQueue = DispatchQueue.main
全局列隊
let globalQueue = DispatchQueue.global()
用戶創建列隊
創建自己的列隊,簡單的方式就是指定列隊的名稱即可
let queue = DispatchQueue(label: "com.conpanyName.queue")
這樣的初始化的列隊有着默認的配置項,默認的列隊是串行列隊。便捷構造函數如下
public convenience init(label: String, qos: DispatchQoS = default, attributes: DispatchQueue.Attributes = default, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = default, target: DispatchQueue? = default)
我們也可以自己顯示設置相關屬性,創建一個並發列隊
let label = "com.conpanyName.queue"
let qos = DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequnecy = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequnecy, target: nil)
參數介紹
label:列隊的標識符,能夠方便區分列隊進行調試
qos:列隊的優先級(quality of service),其值如下:
public struct DispatchQoS : Equatable {
public static let background: DispatchQoS
public static let utility: DispatchQoS
public static let `default`: DispatchQoS
public static let userInitiated: DispatchQoS
public static let userInteractive: DispatchQoS
public static let unspecified: DispatchQoS
}
優先級由最低的background到最高的userInteractive共五個,還有一個為定義的unspecified.
background:最低優先級,等同於DISPATCH_QUEUE_PRIORITY_BACKGROUND. 用戶不可見,比如:在后台存儲大量數據
utility:優先級等同於DISPATCH_QUEUE_PRIORITY_LOW,可以執行很長時間,再通知用戶結果。比如:下載一個大文件,網絡,計算
default:默認優先級,優先級等同於DISPATCH_QUEUE_PRIORITY_DEFAULT,建議大多數情況下使用默認優先級
userInitiated:優先級等同於DISPATCH_QUEUE_PRIORITY_HIGH,需要立刻的結果
.userInteractive:用戶交互相關,為了好的用戶體驗,任務需要立馬執行。使用該優先級用於UI更新,事件處理和小工作量任務,在主線程執行。
Qos指定了列隊工作的優先級,系統會根據優先級來調度工作,越高的優先級能夠越快被執行,但是也會消耗功能,所以准確的指定優先級能夠保證app有效的使用資源。詳細可以看這里
attributes:列隊的屬性,也可以說是類型,即是並發還是串行。attributes是一個結構體並遵守OptionSet協議,所以傳入的參數可以為[.option1, .option2]
public struct Attributes : OptionSet {
public let rawValue: UInt64
public init(rawValue: UInt64)
public static let concurrent: DispatchQueue.Attributes
public static let initiallyInactive: DispatchQueue.Attributes
}
默認:列隊是串行的
.concurrent:列隊是並發的
.initiallyInactive:列隊不會自動執行,需要開發中手動觸發
autoreleaseFrequency:自動釋放頻率,有些列隊會在執行完任務之后自動釋放,有些是不會自動釋放的,需要手動釋放。
簡單看一下列隊優先級
DispatchQueue.global(qos: .background).async {
for i in 1...5 {
print("background: \(i)")
}
}
DispatchQueue.global(qos: .default).async {
for i in 1...5 {
print("default: \(i)")
}
}
DispatchQueue.global(qos: .userInteractive).async {
for i in 1...5 {
print("userInteractive: \(i)")
}
}
執行結果:
default: 1
userInteractive: 1
background: 1
default: 2
userInteractive: 2
background: 2
userInteractive: 3
default: 3
userInteractive: 4
userInteractive: 5
default: 4
background: 3
default: 5
background: 4
background: 5
DispatchWorkItem
DispatchWorkItem是用於幫助DispatchQueue來執行列隊中的任務。類的相關內容如下:
public class DispatchWorkItem {
public init(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, block: @escaping @convention(block) () -> Swift.Void)
public func perform()
public func wait()
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
public func notify(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, queue: DispatchQueue, execute: @escaping @convention(block) () -> Swift.Void)
public func notify(queue: DispatchQueue, execute: DispatchWorkItem)
public func cancel()
public var isCancelled: Bool { get }
}
一般情況下,我們開啟一個異步線程,會這樣創建列隊並執行async方法,以閉包的方式提交任務。
DispatchQueue.global().async
// do async task
}
但是Swift3中使用了DispatchWorkItem類將任務封裝成為對象,由對象進行任務。
let item = DispatchWorkItem {
// do task
}
DispatchQueue.global().async(execute: item)
當然,這里也可以使用DispatchWorkItem實例對象的perform方法執行任務
let workItem = DispatchWorkItem {
// do task
}
DispatchQueue.global().async {
workItem.perform()
}
但是對比一下兩種方式,顯然第一種更加簡潔,方便。
執行任務結束通過nofify獲得通知
let workItem = DispatchWorkItem {
// do async task
print(Thread.current)
}
DispatchQueue.global().async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// update UI
print(Thread.current)
}
使用wait等待任務執行完成
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
let workItem = DispatchWorkItem {
sleep(5)
print("done")
}
queue.async(execute: workItem)
print("before waiting")
workItem.wait()
print("after waiting")
執行結果:
before waiting
done
after waiting
也可以在初始化的時候指定更多的參數
let item = DispatchWorkItem(qos: .default, flags: .barrier) {
// do task
}
第一個參數同樣說優先級,第二個參數指定flag
public struct DispatchWorkItemFlags : OptionSet, RawRepresentable {
public let rawValue: UInt
public init(rawValue: UInt)
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
}
barrier
假如我們有一個並發的列隊用來讀寫一個數據對象,如果這個列隊的操作是讀,那么可以同時多個進行。如果有寫的操作,則必須保證在執行寫操作時,不會有讀取的操作執行,必須等待寫操作完成之后再開始讀取操作,否則會造成讀取的數據出錯,經典的讀寫問題。這里我們就可以使用barrier:
let item = DispatchWorkItem(qos: .default, flags: .barrier) {
// write data
}
let dataQueue = DispatchQueue(label: "com.data.queue", attributes: .concurrent)
dataQueue.async(execute: item)
字典的讀寫操作
private let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
private var dictionary: [String: Any] = [:]
public func set(_ value: Any?, forKey key: String) {
// .barrier flag ensures that within the queue all reading is done
// before the below writing is performed and
// pending readings start after below writing is performed
concurrentQueue.async(flags: .barrier) {
self.dictionary[key] = value
}
}
public func object(forKey key: String) -> Any? {
var result: Any?
concurrentQueue.sync {
result = dictionary[key]
}
// returns after concurrentQueue is finished operation
// beacuse concurrentQueue is run synchronously
return result
}
通過在並發代碼中使用barrier將能夠保證寫操作在所有讀取操作完成之后進行,而且確保寫操作執行完成之后再開始后續的讀取操作。具體的詳情看這里
延時處理
使用asyncAfter來提交任務進行延遲。之前是使用dispatch_time,現在是使用DispatchTime對象表示。可以使用靜態方法now獲得當前時間,然后再通過加上DispatchTimeInterval枚舉獲得一個需要延遲的時間。注意:僅僅是用於在具體時間執行任務,不要在資源競爭的情況下使用。並且在主列隊使用。
let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10)
DispatchQueue.main.asyncAfter(deadline: delay) {
// 延遲執行
}
我們可以進一步簡化,直接添加時間
let delay = DispatchTime.now() + 10
DispatchQueue.main.asyncAfter(deadline: delay) {
// 延遲執行
}
因為在DispatchTime中自定義了“+”號。
public func +(time: DispatchTime, seconds: Double) -> DispatchTime
更多有關延時操作看這里
DispatchGroup
DispatchGroup用於管理一組任務的執行,然后監聽任務的完成,進而執行后續操作。比如:同一個頁面發送多個網絡請求,等待所有結果請求成功刷新UI界面。一般的操作如下:
let queue = DispatchQueue.global()
let group = DispatchGroup()
queue.async(group: group) {
print("Task one finished")
}
queue.async(group: group) {
print("Task two finished")
}
queue.async(group: group) {
print("Task three finished")
}
group.notify(queue: queue) {
print("All task has finished")
}
打印如下:
Task three finished
Task two finished
Task one finished
All task has finished
由於是並發執行異步任務,所以任務的先后次序是不一定的,看起來符合我們的需求,最后接受通知然后可以刷新UI操作。但是真實的網絡請求是異步、耗時的,並不是立馬就返回,所以我們使用asyncAfter模擬延時看看,將任務1延時一秒執行:
let queue = DispatchQueue.global()
let group = DispatchGroup()
queue.async(group: group) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
print("Task one finished")
})
}
queue.async(group: group) {
print("Task two finished")
}
queue.async(group: group) {
print("Task three finished")
}
group.notify(queue: queue) {
print("All task has finished")
}
結果卻不是我們預期的那樣,輸出結果如下:
Task two finished
Task three finished
All task has finished
Task one finished
所以,為了真正實現預期的效果,我們需要配合group的enter和leave兩個函數。每次執行group.enter()表示一個任務被加入到列隊組group中,此時group中的任務的引用計數會加1,當使用group.leave() ,表示group中的一個任務完成,group中任務的引用計數減1.當group列隊組里面的任務引用計數為0時,會通知notify函數,任務執行完成。注意:enter()和leave()成對出現的。
let queue = DispatchQueue.global()
let group = DispatchGroup()
group.enter()
queue.async(group: group) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
print("Task one finished")
group.leave()
})
}
group.enter()
queue.async(group: group) {
print("Task two finished")
group.leave()
}
group.enter()
queue.async(group: group) {
print("Task three finished")
group.leave()
}
group.notify(queue: queue) {
print("All task has finished")
}
這下OK了,輸出跟預期一樣。當然這里也可以使用信號量實現,后面會介紹。
Task three finished
Task two finished
Task one finished
All task has finished
信號量
對於信號量的具體內容,可以看我之前寫的一篇博文。使用起來很簡單,創建信號量對象,調用signal方法發送信號,信號加1,調用wait方法等待,信號減1.現在也適用信號量實現剛剛的多個請求功能。
let queue = DispatchQueue.global()
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 0)
queue.async(group: group) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
semaphore.signal()
print("Task one finished")
})
semaphore.wait()
}
queue.async(group: group) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8, execute: {
semaphore.signal()
print("Task two finished")
})
semaphore.wait()
}
queue.async(group: group) {
print("Task three finished")
}
group.notify(queue: queue) {
print("All task has finished")
}
Suspend / Resume
Suspend可以掛起一個線程,即暫停線程,但是仍然暫用資源,只是不執行
Resume回復線程,即繼續執行掛起的線程。
循環執行任務
之前使用GCD的dispatch_apply()執行多次任務,現在是調用concurrentPerform(),下面是並發執行5次
DispatchQueue.concurrentPerform(iterations: 5) {
print("\($0)")
}
DispatchSource
DispatchSource提高了相關的API來監控低級別的系統對象,比如:Mach ports, Unix descriptors, Unix signals, VFS nodes。並且能夠異步提交事件到派發列隊執行。
簡單定時器
// 定時時間
var timeCount = 60
// 創建時間源
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: .seconds(1))
timer.setEventHandler {
timeCount -= 1
if timeCount <= 0 { timer.cancel() }
DispatchQueue.main.async {
// update UI or other task
}
}
// 啟動時間源
timer.resume()
對於比使用Timer的好處可以看這里
應用場景
多個任務依次執行
最容易想到的就是創建一個串行列隊,然后添加任務到列隊執行。
let serialQueue = DispatchQueue(label: "com.my.queue")
serialQueue.async {
print("task one")
}
serialQueue.async {
print("task two")
}
serialQueue.async {
print("task three")
}
其次就是使用前面講到的DispatchGroup。
取消DispatchWorkItem的任務
直接取消任務
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
let workItem = DispatchWorkItem {
print("done")
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
queue.async(execute: workItem) // not work
}
workItem.cancel()
直接調用取消,異步任務不會執行。
執行的過程中取消任務
func cancelWork() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem!
// create work item
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10_000_000 {
if item.isCancelled { break }
print(i)
self?.heavyWork()
}
item = nil // resolve strong reference cycle
}
// start it
queue.async(execute: item)
// after five seconds, stop it if it hasn't already
queue.asyncAfter(deadline: .now() + 5) { [weak item] in
item?.cancel()
}
}
注意事項
線程死鎖
不要在主列隊中執行同步任務,這樣會造成死鎖問題。
特性
GCD可用於多核的並行運算
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
用法
異步執行回主線程寫法
DispatchQueue.global().async {
print("async do something\(Thread.current)")
DispatchQueue.main.async {
print("come back to main thread\(Thread.current)")
}
}
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參數設置。
DispatchWorkItem
let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
<#code#>
}
after
let deadline = DispatchTime.now() + 5.0
DispatchQueue.global().asyncAfter(deadline: deadline) {
<#code#>
}
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")
}
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()
}
}
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")
}
}
作者:SealShile
鏈接:https://www.jianshu.com/p/96032a032c7c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。