Swift多線程:GCD進(jìn)階绝淡,單例、信號(hào)量苍姜、任務(wù)組

其實(shí)這個(gè)標(biāo)題不知道怎么寫了牢酵,都很碎,也沒有想到特別合適的例子能夠全部放在一起的衙猪。索性就這么平鋪開吧馍乙。

image.png

1. dispatch_once,以及Swift下的單例

使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次屈嗤。所以在通常在OC時(shí)代潘拨,我們都會(huì)用它來寫單例。

但是饶号,但是,但是:這個(gè)函數(shù)在Swift3.0以后的時(shí)代已經(jīng)被刪除了季蚂。沒錯(cuò)茫船,被刪除了,不用了扭屁。

原來自從Swift 1.x開始Swift就已經(jīng)開始用dispatch_one機(jī)制在后臺(tái)支持線程安全的全局lazy初始化和靜態(tài)屬性算谈。static var背后已經(jīng)在使用dispatch_once了,所以從Swift 3開始料滥,就干脆把dispatch_once顯式的取消了然眼。

凸(艸皿艸 ),那Swift里面的單例怎么寫吶葵腹?其實(shí)方法有很多種高每,有OC心Swift皮的寫法、新瓶裝老酒的寫法践宴,那既然咱們開始了Swift鲸匿,就拋下過去那寫沉重包袱吧。這里非典型技術(shù)宅只分享其中的一種阻肩。

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

什么带欢?你在搞事情吧,就這么點(diǎn)?是的乔煞,因?yàn)槭侨肿兞坑蹼灾粫?huì)創(chuàng)建一次。

  • 使用final渡贾,將這個(gè)單例類終止繼承逗宜。
  • 設(shè)置初始化方法為私有,避免外部對(duì)象通過訪問init方法創(chuàng)建單例類的實(shí)例剥啤。

2. dispatch_after

在GCD中我們使用dispatch_after()函數(shù)來延遲執(zhí)行隊(duì)列中的任務(wù)锦溪。準(zhǔn)確的理解是,等到指定的時(shí)間到了以后府怯,才會(huì)開辟一個(gè)新的線程然后立即執(zhí)行隊(duì)列中的任務(wù)刻诊。

所以dispatch_after不會(huì)阻塞當(dāng)前任務(wù),并不是先把任務(wù)加到線程里面,等時(shí)間到了在執(zhí)行牺丙。而是等時(shí)間了则涯,才加入到線程中。

我們使用兩種時(shí)間格式來看看冲簿。

方法一:使用相對(duì)時(shí)間粟判,DispatchTime

@IBAction func delayProcessDispatchTime(_ sender: Any) {
    //dispatch_time用于計(jì)算相對(duì)時(shí)間,當(dāng)設(shè)備睡眠時(shí),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")
    }
}

方法二:使用絕對(duì)時(shí)間峦剔,DispatchWallTime

@IBAction func delayProcessDispatchWallTime(_ sender: Any) {
    //dispatch_walltime用于計(jì)算絕對(duì)時(shí)間档礁。
    let delaytimeInterval = Date().timeIntervalSinceNow + 2.0
    let nowTimespec = timespec(tv_sec: __darwin_time_t(delaytimeInterval), tv_nsec: 0)
    let delayWalltime = DispatchWallTime(timespec: nowTimespec)
    
    //wallDeadline需要一個(gè)DispatchWallTime類型。創(chuàng)建DispatchWallTime類型吝沫,需要timespec的結(jié)構(gòu)體呻澜。
    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. 隊(duì)列的循環(huán)、掛起惨险、恢復(fù)

3.1 dispatch_apply

dispatch_apply函數(shù)是用來循環(huán)來執(zhí)行隊(duì)列中的任務(wù)的羹幸。在Swift 3.0里面對(duì)這個(gè)做了一些優(yōu)化,使用以下方法:

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

本來循環(huán)執(zhí)行就是為了節(jié)約時(shí)間的嘛辫愉,所以默認(rèn)就是用了并行隊(duì)列栅受。我們嘗試一下用這個(gè)升級(jí)版的dispatch_apply讓它執(zhí)行10次打印任務(wù)。

@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.")

}

運(yùn)行結(jié)果如下:

image.png

看恭朗,是不是所有的任務(wù)都是并行進(jìn)行的屏镊?標(biāo)紅的地方,是非典型技術(shù)宅想提醒一下大家這里還是有一些任務(wù)是在主線程中進(jìn)行的冀墨。它循環(huán)執(zhí)行并行隊(duì)列中的任務(wù)時(shí)闸衫,會(huì)開辟新的線程,不過有可能會(huì)在當(dāng)前線程中執(zhí)行一些任務(wù)诽嘉。

如果需要循環(huán)的任務(wù)里面有特別耗時(shí)的操作蔚出,我們上一篇文章里面說是應(yīng)該放在global里面的弟翘。如何避免在主線程操作這個(gè)吶?骄酗?稀余?

來,給三秒時(shí)間想想趋翻。
看到調(diào)用這個(gè)方法的時(shí)候是不是就是在UI線程里面這么寫下來的嘛睛琳?那就開啟一個(gè)gloablQueue,讓它來進(jìn)行不就好了嘛踏烙!BINGO师骗!
這位同學(xué),你已經(jīng)深得真諦讨惩,可以放學(xué)后到我家后花園來了辟癌。嘿嘿?(? ? ??)嘿嘿

3.2 隊(duì)列的掛起與喚醒

如果一大堆任務(wù)執(zhí)行著的時(shí)候,突然后面的任務(wù)不想執(zhí)行的荐捻。那怎么辦吶黍少?我們可以讓它暫時(shí)先掛起,等想好了再讓它們運(yùn)行起來处面。

不過掛起是不會(huì)暫停正在執(zhí)行的隊(duì)列的哈厂置,只能是掛起還沒執(zhí)行的隊(duì)列。

@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)) {
        //            喚醒魂角,開始執(zhí)行
        queue.resume()
    }
}
image.png

我們也可以看一下控制條的打印結(jié)果昵济。顯然能看到代碼并沒有按照順序執(zhí)行,新建的queue里面的打印是在被喚醒之后才執(zhí)行的野揪。

4. 信號(hào)量(semaphore)

信號(hào)量這個(gè)東西在之前的文章里面有一個(gè)例子里面用到了砸紊,當(dāng)時(shí)還有人專門問我semaphore是什么東西。現(xiàn)在可以好好說一說這個(gè)了囱挑。

不要問我是哪個(gè)例子里面用到了,實(shí)在想不起來了呀沼溜,只能記得有人問過semaphore這個(gè)平挑。

有時(shí)候多個(gè)線程對(duì)一個(gè)數(shù)據(jù)進(jìn)行操作的時(shí)候,會(huì)造成一些意想不到的效果系草。多個(gè)人同時(shí)對(duì)同一個(gè)數(shù)據(jù)進(jìn)行操作通熄,誰知道怎么搞啊找都!

為了保證同時(shí)只有一個(gè)線程來修改這個(gè)數(shù)據(jù)唇辨,這個(gè)時(shí)候我們就要用到信號(hào)量了。當(dāng)信號(hào)量為0的時(shí)候能耻,其他線程想要修改或者使用這個(gè)數(shù)據(jù)就必須要等待了赏枚,等待多久吶亡驰?DispatchTime.distantFuture,要等待這么久饿幅。意思就是一直等待下去凡辱。。栗恩。透乾。OC里面管這個(gè)叫做DISPATCH_TIME_FOREVER

如果給信號(hào)量設(shè)置成了0磕秤,其實(shí)就意味著這個(gè)資源沒有人能夠再能用了乳乌。所以,當(dāng)用完了之后一定要把信號(hào)量設(shè)置成非0( ⊙ o ⊙ )市咆!

//創(chuàng)建一個(gè)信號(hào)量汉操,初始值為1
let semaphoreSignal = DispatchSemaphore(value: 1)

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

//表示信號(hào)量+1
semaphoreSignal.signal() 

4.1 簡單實(shí)用一下

我們簡單的讓globalQueue這個(gè)全局隊(duì)列按照1->5的順序進(jìn)行打印,打印一次休息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)次執(zhí)行.\n")
            semaphoreSignal.signal()
        }
        print("測(cè)試打印")
        
    }
    
}

看一下打印結(jié)果:

image.png

globalQueue 如果不加信號(hào)量客情,正常打印是什么樣子的?如果不記得癞己,請(qǐng)看上一篇文章膀斋。iOS多線程系列之三:使用GCD實(shí)現(xiàn)異步下載圖片

好奇寶寶們有沒有想過痹雅,在創(chuàng)建信號(hào)量的時(shí)候初始值設(shè)置成2或者更大的數(shù)仰担,例如50,會(huì)是什么效果绩社? 自己敲敲代碼試試嘍摔蓝,想想看。

4.2 多個(gè)線程之間進(jìn)行任務(wù)協(xié)調(diào)

實(shí)際工作中愉耙,很多時(shí)候我們需要在多個(gè)任務(wù)之間進(jìn)行協(xié)調(diào)贮尉,每個(gè)任務(wù)都是多線程的。

打個(gè)比方朴沿,我們?cè)诤笈_(tái)下載音樂猜谚、專輯的封面。等著兩個(gè)都做完了赌渣,才通知用戶可以去聽音樂了魏铅。兩個(gè)任務(wù)都是多線程,我們其實(shí)并不知道什么時(shí)候才能執(zhí)行完畢坚芜。這個(gè)時(shí)候览芳,就可以靠信號(hào)量,讓大家互相等待鸿竖。

為了更簡化這個(gè)過程沧竟,例子里面模擬了一個(gè)在另外一個(gè)方法中需要耗時(shí)1秒的一個(gè)操作铸敏。當(dāng)完成之后,才執(zhí)行后續(xù)操作屯仗。

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

這個(gè)例子不是用group也可以做嘛搞坝?!是噠魁袜。也可以桩撮。

5. 任務(wù)組

GCD的任務(wù)組在開發(fā)中是經(jīng)常被使用到,當(dāng)需要一組任務(wù)結(jié)束后再執(zhí)行一些操作時(shí)峰弹,就可以用它啦店量。

DispatchGroup的職責(zé)就是當(dāng)隊(duì)列中的所有任務(wù)都執(zhí)行完畢后,會(huì)發(fā)出一個(gè)通知來告訴告訴大家鞠呈,任務(wù)組中所執(zhí)行的隊(duì)列中的任務(wù)執(zhí)行完畢了融师。

既然是組,里面就肯定有很多隊(duì)列啦蚁吝,不然怎么能叫做“組”吶旱爆。

隊(duì)列和組關(guān)聯(lián)有兩種方式:手動(dòng)、自動(dòng)窘茁。

5.1 自動(dòng)關(guān)聯(lián)

肯定先從自動(dòng)開始了怀伦,因?yàn)橥ǔW詣?dòng)最省事啊。這還用問嘛山林。

@IBAction func useGroupQueue(_ sender: UIButton) {
    let group = DispatchGroup()
    //模擬循環(huán)建立幾個(gè)全局隊(duì)列
    for index in 0...3 {

//創(chuàng)建隊(duì)列的同時(shí)房待,加入到任務(wù)組中        
DispatchQueue.global().async(group: group, execute: DispatchWorkItem.init(block: {
            Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
            print("任務(wù)\(index)執(zhí)行完畢")
        }))
    }
    
    //組中所有任務(wù)都執(zhí)行完了會(huì)發(fā)送通知
    group.notify(queue: DispatchQueue.main) {
        print("任務(wù)組的任務(wù)都已經(jīng)執(zhí)行完畢啦!")
    }
    
    
    print("打印測(cè)試一下")
}

看看打印結(jié)果:

image.png

5.2 手動(dòng)關(guān)聯(lián)

接下來我們將手動(dòng)的管理任務(wù)組與隊(duì)列中的關(guān)系驼抹。

enter()桑孩,leave()是一對(duì)兒。前者表示進(jìn)入到任務(wù)組框冀。后者表示離開任務(wù)組流椒。

let manualGroup = DispatchGroup()
//模擬循環(huán)建立幾個(gè)全局隊(duì)列
for manualIndex in 0...3 {
    
    //進(jìn)入隊(duì)列管理
    manualGroup.enter()
    DispatchQueue.global().async {
        //讓線程隨機(jī)休息幾秒鐘
        Thread.sleep(forTimeInterval: TimeInterval(arc4random_uniform(2) + 1))
        print("-----手動(dòng)任務(wù)\(manualIndex)執(zhí)行完畢")
        
        //配置完隊(duì)列之后,離開隊(duì)列管理
        manualGroup.leave()
    }
}

//發(fā)送通知
manualGroup.notify(queue: DispatchQueue.main) {
    print("手動(dòng)任務(wù)組的任務(wù)都已經(jīng)執(zhí)行完畢啦明也!")
}
image.png

利用任務(wù)組可以完成很多場(chǎng)景的工作镣隶。例如多任務(wù)執(zhí)行完后,統(tǒng)一刷新UI诡右。把刷新UI的操作放在notify里面就好了。

還記得刷新UI用哪個(gè)queue嘛轻猖?hoho~

最后帆吻,所有的代碼都放在這里了:gitHub 下載后給顆Star吧~ 么么噠~(~o ̄3 ̄)~ 愛你們~


iOS多線程系列之一:Operation基礎(chǔ)操作,按優(yōu)先級(jí)加載圖片
iOS多線程系列之二:Operation實(shí)例,異步加載CollectionView圖片
iOS多線程系列之三:使用GCD實(shí)現(xiàn)異步下載圖片
iOS多線程系列之四:GCD進(jìn)階,單例咙边、信號(hào)量猜煮、任務(wù)組
iOS多線程系列之五:使用Thread進(jìn)行多線程間通訊次员,協(xié)調(diào)子線程任務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市王带,隨后出現(xiàn)的幾起案子淑蔚,更是在濱河造成了極大的恐慌,老刑警劉巖愕撰,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刹衫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡搞挣,警方通過查閱死者的電腦和手機(jī)带迟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囱桨,“玉大人仓犬,你說我怎么就攤上這事∩岢Γ” “怎么了搀继?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翠语。 經(jīng)常有香客問我叽躯,道長,這世上最難降的妖魔是什么啡专? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任险毁,我火速辦了婚禮,結(jié)果婚禮上们童,老公的妹妹穿的比我還像新娘畔况。我一直安慰自己,他們只是感情好慧库,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布跷跪。 她就那樣靜靜地躺著,像睡著了一般齐板。 火紅的嫁衣襯著肌膚如雪吵瞻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天甘磨,我揣著相機(jī)與錄音橡羞,去河邊找鬼。 笑死济舆,一個(gè)胖子當(dāng)著我的面吹牛卿泽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滋觉,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼签夭,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼齐邦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起第租,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤措拇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后慎宾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丐吓,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年璧诵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汰蜘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡之宿,死狀恐怖族操,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情比被,我是刑警寧澤色难,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站等缀,受9級(jí)特大地震影響枷莉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尺迂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一笤妙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧噪裕,春花似錦蹲盘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至祭陷,卻和暖如春苍凛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兵志。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工醇蝴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人想罕。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓哑蔫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闸迷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴俘枫。腥沽。 不如我直接引用一個(gè)最簡單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,762評(píng)論 1 17
  • 一鸠蚪、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關(guān)多線程的基本概念今阳。本篇博文介紹的是iOS中常用的幾個(gè)多...
    nuclear閱讀 2,050評(píng)論 6 18
  • 學(xué)習(xí)多線程,轉(zhuǎn)載兩篇大神的帖子茅信,留著以后回顧盾舌!第一篇:關(guān)于iOS多線程,你看我就夠了 第二篇:GCD使用經(jīng)驗(yàn)與技巧...
    John_LS閱讀 621評(píng)論 0 3
  • 在這篇文章中蘸鲸,我將為你整理一下 iOS 開發(fā)中幾種多線程方案妖谴,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 603評(píng)論 0 0
  • 當(dāng)我還未降臨這個(gè)世界酌摇,當(dāng)我的心跳和血液都還屬于你的身體的時(shí)候膝舅,你便開始為我織一件件玲瓏溫暖的毛衣了,小時(shí)候一直穿著...
    慶慶寶貝閱讀 408評(píng)論 0 0