其實(shí)這個(gè)標(biāo)題不知道怎么寫了牢酵,都很碎,也沒有想到特別合適的例子能夠全部放在一起的衙猪。索性就這么平鋪開吧馍乙。
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é)果如下:
看恭朗,是不是所有的任務(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()
}
}
我們也可以看一下控制條的打印結(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é)果:
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é)果:
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í)行完畢啦明也!")
}
利用任務(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ù)