iOS中的多線程技術(shù)

多線程

多線程技術(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í)行完畢吱殉。因此他們相互等待,造成死鎖厘托。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末友雳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铅匹,更是在濱河造成了極大的恐慌沥阱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伊群,死亡現(xiàn)場(chǎng)離奇詭異考杉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)舰始,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)崇棠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丸卷,你說(shuō)我怎么就攤上這事枕稀。” “怎么了谜嫉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵萎坷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我沐兰,道長(zhǎng)哆档,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任住闯,我火速辦了婚禮瓜浸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘比原。我一直安慰自己插佛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布量窘。 她就那樣靜靜地躺著雇寇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚌铜。 梳的紋絲不亂的頭發(fā)上锨侯,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音厘线,去河邊找鬼识腿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛造壮,可吹牛的內(nèi)容都是我干的渡讼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼耳璧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼成箫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旨枯,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蹬昌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后攀隔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體皂贩,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栖榨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了明刷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婴栽。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辈末,靈堂內(nèi)的尸體忽然破棺而出愚争,到底是詐尸還是另有隱情,我是刑警寧澤挤聘,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布轰枝,位于F島的核電站,受9級(jí)特大地震影響组去,放射性物質(zhì)發(fā)生泄漏鞍陨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一添怔、第九天 我趴在偏房一處隱蔽的房頂上張望湾戳。 院中可真熱鬧,春花似錦广料、人聲如沸砾脑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)韧衣。三九已至,卻和暖如春购桑,著一層夾襖步出監(jiān)牢的瞬間畅铭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工勃蜘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留硕噩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓缭贡,卻偏偏與公主長(zhǎng)得像炉擅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阳惹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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