Swift4 - GCD的使用


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

來源:簡書

簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

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



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