多線程
多線程技術(shù)是iOS開(kāi)發(fā)里十分常見(jiàn)的驶兜,下面會(huì)介紹GCD的常用多線程技術(shù)。首先簡(jiǎn)單了解一下幾個(gè)概念:
- 同步,異步:任務(wù)執(zhí)行時(shí)是否開(kāi)多線程嫉入,同步為單線程,異步為多線程
- 串行璧尸,并發(fā):任務(wù)執(zhí)行的方式咒林,串行為順序執(zhí)行,并發(fā)為同時(shí)執(zhí)行
- 隊(duì)列:任務(wù)存放的地方爷光,線程從隊(duì)列里取出任務(wù)執(zhí)行垫竞。分為串行隊(duì)列和并發(fā)隊(duì)列
- 線程:執(zhí)行任務(wù)的實(shí)際運(yùn)作單位,主線程從主隊(duì)列里取任務(wù)蛀序,更新UI必須在主線程完成
GCD:
GCD是操作最簡(jiǎn)單欢瞪,也是我們最常用的多線程技術(shù)。在OC里是一套C語(yǔ)言API徐裸,在Swift里對(duì)其封裝進(jìn)一步簡(jiǎn)化遣鼓,本文會(huì)使用Swift語(yǔ)法。
一重贺。主隊(duì)列骑祟,全局隊(duì)列
先來(lái)看看這個(gè)GCD的最常用公式:
DispatchQueue.global().async {
//long time operation
DispatchQueue.main.async {
//update UI
}
}
它分為以下幾個(gè)步驟:
- 通過(guò)
DispatchQueue.global()
獲取全局隊(duì)列 - 在全局隊(duì)列中
async
并發(fā)執(zhí)行耗時(shí)操作回懦,如下載 - 通過(guò)
DispatchQueue.main
獲取全局隊(duì)列 - 在全局隊(duì)列中
async
并發(fā)執(zhí)行更新UI的動(dòng)作
那么為什么要如此使用呢?
- 首先曾我,更新UI的操作必須放在主線程中粉怕,否則會(huì)報(bào)錯(cuò)。這里
DispatchQueue.main
是獲取主隊(duì)列抒巢,它是一個(gè)串行隊(duì)列贫贝,而主線程要執(zhí)行的任務(wù)就是到這個(gè)隊(duì)列里去取的。 -
DispatchQueue.global()
是獲取系統(tǒng)提供的全局隊(duì)列蛉谜,該隊(duì)列是并發(fā)隊(duì)列稚晚。加入該隊(duì)列的任務(wù)會(huì)開(kāi)辟多條線程共同執(zhí)行,就好像一個(gè)頁(yè)面會(huì)下載顯示很多張圖片型诚,用全局隊(duì)列的話圖片會(huì)一張張顯示出來(lái)客燕,而如果用主隊(duì)列的話,所有圖片下載完成才統(tǒng)一顯示狰贯。 - 這里不管是主隊(duì)列還是全局隊(duì)列都使用
async
也搓,而沒(méi)有使用sync
是因?yàn)?code>sync同步操作,相當(dāng)于將所有操作都由主線程來(lái)執(zhí)行涵紊,這會(huì)阻塞主線程傍妒,也就是UI上的卡頓。
這里寫(xiě)了一個(gè)小實(shí)驗(yàn)比較上面的各種情況摸柄。(因?yàn)閳D片地址在公司服務(wù)器上颤练,這里不公布代碼,只看結(jié)果)驱负。
點(diǎn)擊一個(gè)button嗦玖,會(huì)Push到下一個(gè)頁(yè)面,而下一個(gè)頁(yè)面中會(huì)有一個(gè)CollectionView,共包含10個(gè)CollectionViewCell,每一個(gè)cell中都會(huì)下載并顯示一張圖片跃脊。
-
第一張圖用的是之前的標(biāo)準(zhǔn)公式宇挫,點(diǎn)擊按鈕后立即Push到下一個(gè)頁(yè)面,并且圖片也相繼顯示出來(lái)匾乓。
GCDDemo1.gif
-
第二張圖是將
DispatchQueue.global()
換成了DispatchQueue.main()
捞稿。可以看到拼缝,點(diǎn)擊按鈕后立即Push到下一個(gè)頁(yè)面,所有圖片下載完成后再一起顯示彰亥,這是因?yàn)橹麝?duì)列是串行隊(duì)列咧七,所有的更新UI任務(wù)都排在了下載任務(wù)之后,因此要等所有的圖片下載完成后任斋,UI才會(huì)開(kāi)始顯示继阻。[圖片上傳中...(GCDDemo3.gif-83a055-1564041778608-0)]
-
第三張圖是將
DispatchQueue.global().async
換成了DispatchQueue.global().sync
。可以看到瘟檩,點(diǎn)擊按鈕后卡頓了一會(huì)兒抹缕,再進(jìn)入下一個(gè)頁(yè)面。這是因?yàn)?code>sync是同步操作墨辛,會(huì)阻塞主線程卓研。GCDDemo3.gif
由以上三張圖可看出,常用公式是最合適的選擇
二睹簇。創(chuàng)建隊(duì)列
let queue = DispatchQueue(label: "com.demo.queue", qos: .background, attributes: .concurrent)
- qos是指隊(duì)列的優(yōu)先級(jí)奏赘,從background到userinteractive,優(yōu)先級(jí)從低到高
- concurrent指定了該隊(duì)列為并發(fā)隊(duì)列,若不穿太惠,則默認(rèn)為串行隊(duì)列
自建的隊(duì)列最終會(huì)被歸入到主隊(duì)列和全局隊(duì)列中去磨淌,那么為什么還要?jiǎng)?chuàng)建它們呢,也是便于對(duì)一系列任務(wù)的管理凿渊。
三梁只。派發(fā)組DispatchGroup
開(kāi)發(fā)中也許會(huì)有這一種情況,我們要同時(shí)請(qǐng)求好幾個(gè)接口后埃脏,再刷新UI搪锣。這種對(duì)多個(gè)異步請(qǐng)求的管理,用DispatchGroup最合適剂癌。
用法如下
let group = DispatchGroup()
group.enter()
//task1
group.leave()
group.enter()
//task2
group.leave()
group.enter()
//task3
group.leave()
group.notify(queue: DispatchQueue.main) {
//update UI
print("更新UI")
}
更新UI的操作會(huì)在task1,task2,task3這3個(gè)異步任務(wù)完成后再執(zhí)行淤翔。
四。信號(hào)量semaphore
簡(jiǎn)單的說(shuō)佩谷,在異步操作中旁壮,任務(wù)完成的順序是不確定的。semaphore可以使得我們將異步操作按順序同步完成谐檀。
這里有一片博客抡谐,對(duì)其進(jìn)行了詳細(xì)的介紹
五。屏障barrier
想象有這樣一個(gè)操作桐猬。從數(shù)據(jù)庫(kù)里執(zhí)行兩次讀的任務(wù)read1和read2麦撵,并發(fā)執(zhí)行并沒(méi)有什么問(wèn)題±7荆可如果要在read1和read2中間加入一個(gè)write1,要求read1讀取的是write之前的數(shù)據(jù)免胃,read2讀取的是write之后的數(shù)據(jù),那么應(yīng)該如何處理呢惫撰?
首先羔沙,如果write不能用普通的并發(fā)操作。因?yàn)椴l(fā)隊(duì)列的特性是無(wú)法確保read1,read2以及write的執(zhí)行順序厨钻,這可能會(huì)發(fā)生read2讀取的是write之前的數(shù)據(jù)扼雏。
這里我們需要用到barrier坚嗜。顧名思義,它是作為一個(gè)屏障诗充,隔開(kāi)了read1和read2苍蔬,在這個(gè)屏障內(nèi)的進(jìn)行write,也會(huì)保證,read1->write->read2這樣的一個(gè)執(zhí)行順序
DispatchQueue.global().async {
//read1
}
DispatchQueue.global().async(flags: .barrier) {
//write
}
DispatchQueue.global().async {
//read2
}
六蝴蜓。延遲執(zhí)行
這里用GCD封裝一個(gè)延遲執(zhí)行
func delay(_ timeInterval: TimeInterval, closure: @escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeInterval, execute: closure)
}
調(diào)用起來(lái)非常簡(jiǎn)單和明了
delay(3) {
//task
}
值的一體的是碟绑,該延遲執(zhí)行指的并不是在3秒后執(zhí)行task,而指的是,在3秒后励翼,將task加入主隊(duì)列蜈敢。所以當(dāng)主隊(duì)列有大量處理或者本身已經(jīng)延遲的情況下,真正執(zhí)行的時(shí)間會(huì)比3秒更長(zhǎng)汽抚。不過(guò)一般來(lái)說(shuō)抓狭,在主隊(duì)列非阻塞的情況下,該方法還是算非常簡(jiǎn)潔有效的造烁。
七否过。創(chuàng)建單例
得益于GCD中的dispatch_once
,這個(gè)函數(shù)會(huì)讓程序只執(zhí)行一次,我們?cè)贠C中用它來(lái)實(shí)現(xiàn)單例。
static dispatch_once_t pred;
dispatch_once(pred, ^{
//init
});
不過(guò)為了防止單例類(lèi)通過(guò)alloc或者new的方法實(shí)例化惭蟋,實(shí)際上要寫(xiě)的代碼還有很多苗桂,相比較下來(lái)Swift的單例實(shí)現(xiàn)就要簡(jiǎn)單很多。
class Singleton {
static let shared = Singleton()
private init() {}
}
八告组。死鎖
死鎖一般是指同步任務(wù)的相互等待煤伟,造成程序崩潰。
例如木缝,我們?cè)赩iewController的ViewDidLoad
里加入這一句便锨,程序執(zhí)行到這里立刻就會(huì)崩潰。
DispatchQueue.main.sync {}
因?yàn)閂iewDidLoad是從主線程執(zhí)行我碟,而DispatchQueue.main.sync {}
也是在主線程里同步執(zhí)行放案。ViewDidLoad需要等待DispatchQueue.main.sync {}完成,但ViewDidLoad又是先加入主隊(duì)列的矫俺,因此DispatchQueue.main.sync {}要執(zhí)行必須先等ViewDidLoad執(zhí)行完畢吱殉。因此他們相互等待,造成死鎖厘托。