Swift多線程:GCD進階,單例、信號量、任務組

其實這個標題不知道怎麼寫了,都很碎,也沒有想到特別合適的例子可以所有放在一塊兒的。索性就這麼平鋪開吧。git

1. dispatch_once,以及Swift下的單例

使用dispatch_once函數能保證某段代碼在程序運行過程當中只被執行1次。因此在一般在OC時代,咱們都會用它來寫單例。github

可是,可是,可是:這個函數在Swift3.0之後的時代已經被刪除了。沒錯,被刪除了,不用了。api

原來自從Swift 1.x開始Swift就已經開始用dispatch_one機制在後臺支持線程安全的全局lazy初始化和靜態屬性。static var背後已經在使用dispatch_once了,因此從Swift 3開始,就乾脆把dispatch_once顯式的取消了。安全

凸(艹皿艹 ),那Swift裏面的單例怎麼寫吶?其實方法有不少種,有OC心Swift皮的寫法、新瓶裝老酒的寫法,那既然我們開始了Swift,就拋下過去那寫沉重包袱吧。這裏非典型技術宅只分享其中的一種。bash

final class SingleTon: NSObject {
    static let shared = SingleTon()
    private override init() {}
}
複製代碼

什麼?你在搞事情吧,就這麼點?是的,由於是全局變量,因此只會建立一次。多線程

  • 使用final,將這個單例類終止繼承。
  • 設置初始化方法爲私有,避免外部對象經過訪問init方法建立單例類的實例。

2. dispatch_after

在GCD中咱們使用dispatch_after()函數來延遲執行隊列中的任務。準確的理解是,等到指定的時間到了之後,纔會開闢一個新的線程而後當即執行隊列中的任務。app

因此dispatch_after不會阻塞當前任務,並非先把任務加到線程裏面,等時間到了在執行。而是等時間了,才加入到線程中。dom

咱們使用兩種時間格式來看看。異步

方法一:使用相對時間,DispatchTimeasync

@IBAction func delayProcessDispatchTime(_ sender: Any) {
    //dispatch_time用於計算相對時間,當設備睡眠時,dispatch_time也就跟着睡眠了.
    //Creates a `DispatchTime` relative to the system clock that ticks since boot.
    let time = DispatchTimeInterval.seconds(3)
    let delayTime: DispatchTime = DispatchTime.now() + time
    DispatchQueue.global().asyncAfter(deadline: delayTime) {
        Thread.current.name = "dispatch_time_Thread"
        print("Thread Name: \(String(describing: Thread.current.name))\n dispatch_time: Deplay \(time) seconds.\n")
    }
}
複製代碼

方法二:使用絕對時間,DispatchWallTime

@IBAction func delayProcessDispatchWallTime(_ sender: Any) {
    //dispatch_walltime用於計算絕對時間。
    let delaytimeInterval = Date().timeIntervalSinceNow + 2.0
    let nowTimespec = timespec(tv_sec: __darwin_time_t(delaytimeInterval), tv_nsec: 0)
    let delayWalltime = DispatchWallTime(timespec: nowTimespec)
    
    //wallDeadline須要一個DispatchWallTime類型。建立DispatchWallTime類型,須要timespec的結構體。
    DispatchQueue.global().asyncAfter(wallDeadline: delayWalltime) {
        Thread.current.name = "dispatch_Wall_time_Thread"
        print("Thread Name: \(String(describing: Thread.current.name))\n dispatchWalltime: Deplay \(delaytimeInterval) seconds.\n")
    }
    
}
複製代碼

3. 隊列的循環、掛起、恢復

3.1 dispatch_apply

dispatch_apply函數是用來循環來執行隊列中的任務的。在Swift 3.0裏面對這個作了一些優化,使用如下方法:

public class func concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void)
複製代碼

原本循環執行就是爲了節約時間的嘛,因此默認就是用了並行隊列。咱們嘗試一下用這個升級版的dispatch_apply讓它執行10次打印任務。

@IBAction func useDispatchApply(_ sender: Any) {

        print("Begin to start a DispatchApply")
        DispatchQueue.concurrentPerform(iterations: 10) { (index) in
            
            print("Iteration times:\(index),Thread = \(Thread.current)")
        }
        
        print("Iteration have completed.")

}
複製代碼

運行結果以下:

image.png

看,是否是全部的任務都是並行進行的?標紅的地方,是非典型技術宅想提醒一下你們這裏仍是有一些任務是在主線程中進行的。它循環執行並行隊列中的任務時,會開闢新的線程,不過有可能會在當前線程中執行一些任務。

若是須要循環的任務裏面有特別耗時的操做,咱們上一篇文章裏面說是應該放在global裏面的。如何避免在主線程操做這個吶???

來,給三秒時間想一想。 看到調用這個方法的時候是否是就是在UI線程裏面這麼寫下來的嘛?那就開啓一個gloablQueue,讓它來進行不就行了嘛!BINGO! 這位同窗,你已經深得真諦,能夠放學後到我家後花園來了。嘿嘿✧(≖ ◡ ≖✿)嘿嘿

3.2 隊列的掛起與喚醒

若是一大堆任務執行着的時候,忽然後面的任務不想執行的。那怎麼辦吶?咱們可讓它暫時先掛起,等想好了再讓它們運行起來。

不過掛起是不會暫停正在執行的隊列的哈,只能是掛起還沒執行的隊列。

@IBAction func useDispatchSuspend(_ sender: Any) {
    let queue = DispatchQueue(label: "new thread")
    //        掛起
    queue.suspend()
    
    queue.async {
        print("The queue is suspended. Now it has completed.\n The queue is \"\(queue.label)\". ")
    }
    
    print("The thread will sleep for 3 seconds' time")
    
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(3)) {
        //            喚醒,開始執行
        queue.resume()
    }
}
複製代碼

image.png

咱們也能夠看一下控制條的打印結果。顯然能看到代碼並無按照順序執行,新建的queue裏面的打印是在被喚醒以後才執行的。

4. 信號量(semaphore)

信號量這個東西在以前的文章裏面有一個例子裏面用到了,當時還有人專門問我semaphore是什麼東西。如今能夠好好說一說這個了。

不要問我是哪一個例子裏面用到了,實在想不起來了呀,只能記得有人問過semaphore這個。

有時候多個線程對一個數據進行操做的時候,會形成一些意想不到的效果。多我的同時對同一個數據進行操做,誰知道怎麼搞啊!

爲了保證同時只有一個線程來修改這個數據,這個時候咱們就要用到信號量了。當信號量爲0的時候,其餘線程想要修改或者使用這個數據就必需要等待了,等待多久吶?DispatchTime.distantFuture,要等待這麼久。意思就是一直等待下去。。。。OC裏面管這個叫作DISPATCH_TIME_FOREVER

若是給信號量設置成了0,其實就意味着這個資源沒有人可以再能用了。因此,當用完了以後必定要把信號量設置成非0( ⊙ o ⊙ )!

//建立一個信號量,初始值爲1
let semaphoreSignal = DispatchSemaphore(value: 1)

//表示信號量-1
semaphoreSignal.wait()  

//表示信號量+1
semaphoreSignal.signal() 
複製代碼

4.1 簡單實用一下

咱們簡單的讓globalQueue這個全局隊列按照1->5的順序進行打印,打印一次休息1秒鐘。

@IBAction func useSemaphore(_ sender: Any) {
    let semaphoreSignal = DispatchSemaphore(value: 1)
    
    for index in 1...5 {
        DispatchQueue.global().async {
            semaphoreSignal.wait()
            print(Thread.current)
            print("這是第\(index)次執行.\n")
            semaphoreSignal.signal()
        }
        print("測試打印")
        
    }
    
}
複製代碼

看一下打印結果:

image.png

globalQueue 若是不加信號量,正常打印是什麼樣子的?若是不記得,請看上一篇文章。iOS多線程系列之三:使用GCD實現異步下載圖片

好奇寶寶們有沒有想過,在建立信號量的時候初始值設置成2或者更大的數,例如50,會是什麼效果? 本身敲敲代碼試試嘍,想一想看。

4.2 多個線程之間進行任務協調

實際工做中,不少時候咱們須要在多個任務之間進行協調,每一個任務都是多線程的。

打個比方,咱們在後臺下載音樂、專輯的封面。等着兩個都作完了,才通知用戶能夠去聽音樂了。兩個任務都是多線程,咱們其實並不知道何時才能執行完畢。這個時候,就能夠靠信號量,讓你們互相等待。

爲了更簡化這個過程,例子裏面模擬了一個在另一個方法中須要耗時1秒的一個操做。當完成以後,才執行後續操做。

func semaphoreDemo() -> Void {
    let sema = DispatchSemaphore.init(value: 0)
    getListData { (result) in
        if result == true {
            sema.signal()
        }
    }
    sema.wait()
    print("我終於能夠開始幹活了")
}

private func getListData(isFinish:@escaping (Bool) -> ()) {
    
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 1)
        print("global queue has completed!")
        isFinish(true)
    }
    
}
複製代碼

這個例子不是用group也能夠作嘛?!是噠。也能夠。

5. 任務組

GCD的任務組在開發中是常常被使用到,當須要一組任務結束後再執行一些操做時,就能夠用它啦。

DispatchGroup的職責就是當隊列中的全部任務都執行完畢後,會發出一個通知來告訴告訴你們,任務組中所執行的隊列中的任務執行完畢了。

既然是組,裏面就確定有不少隊列啦,否則怎麼能叫作「組」吶。

隊列和組關聯有兩種方式:手動、自動。

5.1 自動關聯

確定先從自動開始了,由於一般自動最省事啊。這還用問嘛。

@IBAction func useGroupQueue(_ sender: UIButton) {
    let group = DispatchGroup()
    //模擬循環創建幾個全局隊列
    for index in 0...3 {

//建立隊列的同時,加入到任務組中        
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
            Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
            print("任務\(index)執行完畢")
        }))
    }
    
    //組中全部任務都執行完了會發送通知
    group.notify(queue: DispatchQueue.main) {
        print("任務組的任務都已經執行完畢啦!")
    }
    
    
    print("打印測試一下")
}
複製代碼

看看打印結果:

image.png

5.2 手動關聯

接下來咱們將手動的管理任務組與隊列中的關係。

enter()leave()是一對兒。前者表示進入到任務組。後者表示離開任務組。

let manualGroup = DispatchGroup()
//模擬循環創建幾個全局隊列
for manualIndex in 0...3 {
    
    //進入隊列管理
    manualGroup.enter()
    DispatchQueue.global().async {
        //讓線程隨機休息幾秒鐘
        Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
        print("-----手動任務\(manualIndex)執行完畢")
        
        //配置完隊列以後,離開隊列管理
        manualGroup.leave()
    }
}

//發送通知
manualGroup.notify(queue: DispatchQueue.main) {
    print("手動任務組的任務都已經執行完畢啦!")
}
複製代碼

image.png

利用任務組能夠完成不少場景的工做。例如多任務執行完後,統一刷新UI。把刷新UI的操做放在notify裏面就行了。

還記得刷新UI用哪一個queue嘛?hoho~

最後,全部的代碼都放在這裏了:gitHub 下載後給顆Star吧~ 麼麼噠~(~o ̄3 ̄)~ 愛大家~

相關文章
相關標籤/搜索