在我們做iOS開發(fā)的過程中税娜,經(jīng)常會與多線程打交道坐搔,異步繪制,網(wǎng)絡(luò)請求等敬矩,方式有NSThread概行,NSOperationQueue,GCD等弧岳,在這里GCD的地位舉足輕重凳忙,那么今天寫一篇關(guān)于GCD的文章。首先迎來第一個問題:
什么是GCD
全名叫 Grand Central Dispatch 是一種異步執(zhí)行任務(wù)的技術(shù)禽炬,一套基于c語言實現(xiàn)的api涧卵,語法十分簡潔,只需要簡單定義任務(wù)或按需加入到隊列中腹尖,就可以按照計劃實現(xiàn)功能柳恐,下面一個簡單的例子。
dispatch_async(dispatch_get_global_queue(0,0),^{//網(wǎng)絡(luò)請求//異步繪制圖像//數(shù)據(jù)庫訪問等dispatch_async(dispatch_get_main_queue(),^{//刷新UI});});
在這里使用異步的方式定義了一個任務(wù)热幔,并將其添加到一個全局隊列中胎撤,這里面的任務(wù)內(nèi)容可以進行一些耗時操作,由于是異步所以不影響主線程断凶,當(dāng)任務(wù)結(jié)束之后,同樣使用異步的方式定義了一個任務(wù)巫俺,將其添加到了主隊列中认烁,這里執(zhí)行的任務(wù)會在主線程完成。
同步和異步
前面內(nèi)容提到了“異步”一詞介汹,那么這里聊一下什么是“異步”却嗡,相應(yīng)的還有“同步”一詞。
異步的作用是不需要CPU立刻響應(yīng)嘹承,而是等待一個信號或回調(diào)來下一步操作窗价,當(dāng)前線程可以立刻執(zhí)行后續(xù)內(nèi)容,而同步為可以阻塞當(dāng)前線程叹卷,當(dāng)執(zhí)行完一個任務(wù)之后才能執(zhí)行下一個撼港。
異步是目的坪它,多線程是手段
當(dāng)我們開啟一個新的線程之后,可以將任務(wù)交由新的線程執(zhí)行帝牡,實現(xiàn)異步效果往毡,當(dāng)需要的時候再返回之前的線程。
用圖來做一個同步和異步的解釋
異步和同步
這里thread1模擬主線程靶溜,當(dāng)執(zhí)行任務(wù)A的時候由于耗時比較短开瞭,所以可以在主線程上完成,當(dāng)執(zhí)行到任務(wù)B的時候罩息,由于耗時長嗤详,所以開辟了一條新的線程,將任務(wù)交由子線程處理瓷炮,同時繼續(xù)完成任務(wù)C葱色,當(dāng)耗時操作完成之后,將結(jié)果返回給主線程進行操作崭别,實現(xiàn)了異步功能冬筒。
使用多線程仍然要注意幾個問題,1.數(shù)據(jù)競爭 2.死鎖 3.線程開辟太多導(dǎo)致內(nèi)存消耗過大茅主,不過只要使用得當(dāng)舞痰,多線程對我們開發(fā)的好處十分巨大。
串行隊列和并發(fā)隊列
首先解釋一下“隊列”诀姚,如其名字响牛,它是執(zhí)行處理的等待隊列,這里的隊列調(diào)度方式為先進先出模式(FIFO-First In First Out)赫段,也就是先添加到隊列中的任務(wù)先執(zhí)行呀打,當(dāng)然還有其他幾種模式,不過這里暫時不考慮糯笙,有興趣的請自行查資料
FIFO
隊列的種類也是兩種贬丛,Serial Dispatch Queue和 Concurrent Dispatch Queue,分別為串行隊列和并發(fā)隊列给涕,串行隊列中的任務(wù)必須按照順序依次執(zhí)行豺憔,并發(fā)隊列可以多個線程以并發(fā)的形式執(zhí)行。
串行隊列和并發(fā)隊列
下面用代碼來看串行隊列和并發(fā)隊列的區(qū)別够庙,首先使用串行隊列的方式創(chuàng)建幾個異步操作
dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_SERIAL);dispatch_async(queue,^{NSLog(@"%@---0",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---1",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---2",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---3",[NSThread currentThread]);});
看看輸出結(jié)果
<NSThread:0x6000002777c0>{number=3,name=(null)}---0<NSThread:0x6000002777c0>{number=3,name=(null)}---1<NSThread:0x6000002777c0>{number=3,name=(null)}---2<NSThread:0x6000002777c0>{number=3,name=(null)}---3
很明顯恭应,這里是同一個線程,而且也確實是按照順序執(zhí)行的耘眨,那么接下來使用并發(fā)隊列來操作昼榛。
dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue,^{NSLog(@"%@---0",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---1",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---2",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---3",[NSThread currentThread]);});
再看看輸出結(jié)果
<NSThread:0x60400046adc0>{number=5,name=(null)}---2<NSThread:0x60400046b000>{number=3,name=(null)}---0<NSThread:0x6000004655c0>{number=4,name=(null)}---1<NSThread:0x6000004653c0>{number=6,name=(null)}---3
很明顯,并沒有按照順序執(zhí)行剔难,而且不是同一個線程胆屿,這就印證了上面所說的串行隊列和并發(fā)隊列的區(qū)別奥喻。
但是不代表創(chuàng)建了多少個并發(fā)任務(wù)就會開辟多少個線程,這個線程數(shù)量由XNU內(nèi)核來決定莺掠,將上面的案例調(diào)整為8個任務(wù)衫嵌,可以看到有些線程是重復(fù)的,表明了如果一開始就可以開辟8個線程彻秆,就不會出現(xiàn)這種情況楔绞,所以當(dāng)一個任務(wù)結(jié)束之后,下一個任務(wù)可以放到完成的線程中唇兑,但是和串行隊列還是有區(qū)別的
多個任務(wù)的并發(fā)隊列
首先會開辟4條線程處理任務(wù)酒朵,將task0-task3異步形式放入線程中執(zhí)行,假定task0首先完成扎附,并發(fā)隊列中取出task4放入線程3中執(zhí)行蔫耽,以此類推,所以也就造成了有些線程是重復(fù)的原因留夜。
DISPATCH MAIN QUEUE/DISPATCH GLOBAL QUEUE
我們平時常用的大多是這兩種匙铡,前者是在主線程執(zhí)行的queue,由于主線程只有一個碍粥,自然是個串行隊列鳖眼,而global_queue是個并發(fā)隊列,這個隊列有4個優(yōu)先級嚼摩,分別是低優(yōu)先級(DISPATCH_QUEUE_PRIORITY_LOW)钦讳,默認(rèn)優(yōu)先級(DISPATCH_QUEUE_PRIORITY_DEFAULT),高優(yōu)先級(DISPATCH_QUEUE_PRIORITY_HIGH)枕面,和后臺優(yōu)先級(
DISPATCH_QUEUE_PRIORITY_BACKGROUND)愿卒,代碼如下
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
GCD的api
前面已經(jīng)提到一些關(guān)于GCD的api,下面會詳細(xì)的探討這些api的功能
dispatch_queue_create
dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.example.gcdDeo",NULL);
這里是創(chuàng)建一個queue的方法潮秘,返回類型為dispatch_queue_t琼开,方法含有兩個參數(shù),一個是隊列名枕荞,推薦使用應(yīng)用程序ID逆序的域名方式稠通,后面參數(shù)表示生成的隊列類型,一種為DISPATCH_QUEUE_SERIAL為串行隊列买猖,這里也可以使用NULL,這個宏定義的值就是NULL滋尉,另一種是DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊列玉控,這里雖然串行隊列中的任務(wù)是按順序執(zhí)行的,但是如果創(chuàng)建多個串行隊列狮惜,是會并行處理高诺。
并行執(zhí)行的多個Serial Dispatch Queue
由于一個隊列只操作一個線程碌识,所以在執(zhí)行一些比較重要的操作時,盡量使用串行隊列虱而,避免造成數(shù)據(jù)沖突筏餐。
dispatch_(a)sync
最常用的就是這兩個函數(shù)了dispatch_sync同步執(zhí)行,dispatch_async異步執(zhí)行牡拇,第一個參數(shù)為指定的隊列名魁瞪,block為執(zhí)行的任務(wù)內(nèi)容,但是使用gcd也要注意一些問題
NSLog(@"1");dispatch_sync(dispatch_get_main_queue(),^{NSLog(@"2");});NSLog(@"3");
1//接下來報錯
同步主隊列的問題
看得出這個問題的原因惠呼,同步的方式在主線程執(zhí)行一個任務(wù)导俘,造成了線程阻塞。
dispatch_after
當(dāng)我們需要在一段時間之后延遲執(zhí)行某些任務(wù)的時候剔蹋,可以使用dispatch_after這個函數(shù)旅薄,示例代碼如下:
NSLog(@"延遲之前");dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3*NSEC_PER_SEC));dispatch_after(time,dispatch_get_main_queue(),^{NSLog(@"延遲3秒執(zhí)行");});
這里的返回結(jié)果為
2018-09-04 14:52:59.437518+0800 GCDDemo[75192:21501573] 延遲之前2018-09-04 14:53:02.438042+0800 GCDDemo[75192:21501573] 延遲3秒執(zhí)行
可以看得出的確是延遲了3秒執(zhí)行的任務(wù),不過這里的實際意義為在3秒后將任務(wù)添加到主隊列中執(zhí)行泣崩,由于Main Dispatch Queue在主線程的Runloop中少梁,所以真實的時間為3秒加上最短立刻最長Runloop一次循環(huán)的時間,并且如果Runloop中堆積的任務(wù)較多矫付,可能時間會更長凯沪,所以時間并不是完全精確,不過想要大致完成延遲功能技即,dispatch_after是完全沒有問題的著洼。
time為dispatch_time_t類型,從第一個參數(shù)時間開始而叼,到第二個指定后的時間結(jié)束身笤,這里NSEC_PER_SEC是時間類型,以秒為單位葵陵,NSEC_PER_MSEC是以毫秒做單位液荸,其他類型單位這里不多解釋了。
dispatch_group
項目中我們經(jīng)常會遇到將多個異步任務(wù)的結(jié)果同時返回脱篙,如果只是同步的話娇钱,執(zhí)行完最后一個任務(wù)就是結(jié)束,但是異步卻不行绊困,所以這里我們需要使用dispatch_group文搂,代碼如下
NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_get_global_queue(0,0);dispatch_group_async(group,queue,^{sleep(4);NSLog(@"子線程1-----%@",[NSThread currentThread]);});dispatch_group_async(group,queue,^{sleep(3);NSLog(@"子線程2-----%@",[NSThread currentThread]);});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});
這里使用sleep模擬了兩個耗時操作,并且打印了幾個位置所處的線程秤朗,首先使用dispatch_group_create創(chuàng)建了一個組煤蹭,并且使用了global queue,接下來模擬異步延時操作,使用dispatch_group_async這個函數(shù)和dispatch_async的功能是一樣的硝皂,只不過前者歸屬于第一個參數(shù)這個組常挚,當(dāng)函數(shù)全部結(jié)束之后會調(diào)用dispatch_group_notify這個方法,表示所有異步操作都已經(jīng)結(jié)束稽物,代碼執(zhí)行結(jié)果如下
2018-09-0416:22:03.449143+0800GCDDemo[76086:21787405]全部開始-----<NSThread:0x6040002601c0>{number=1,name=main}2018-09-04 16:22:06.451174+0800 GCDDemo[76086:21787469] 子線程2-----<NSThread:0x60400027ed40>{number=3,name=(null)}2018-09-04 16:22:07.452564+0800 GCDDemo[76086:21787470] 子線程1-----<NSThread:0x600000470100>{number=4,name=(null)}2018-09-04 16:22:07.452926+0800 GCDDemo[76086:21787405] 全部結(jié)束-----<NSThread:0x6040002601c0>{number=1,name=main}
這里可以看出的確是當(dāng)兩個子線程都完成之后奄毡,才回到主線程的回調(diào)中,另外一種方式通過dispatch_group_wait方式阻止后續(xù)操作贝或,直到異步函數(shù)全部完成
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);
這里wait表示等待吼过,第一個參數(shù)表示等待的對象,類型是dispatch_group_t傀缩,第二個參數(shù)表示等待時間那先,這里使用DISPATCH_TIME_FOREVER表示一直等待,所以這個函數(shù)變成了直到組中代碼全部結(jié)束赡艰,等待才會停止售淡,不過這里還是更加推薦使用dispatch_group_notify方式,因為dispatch_group_wait是同步的慷垮,所以不推薦在主線程中使用揖闸。
dispatch_group_enter/dispatch_group_leave
實際工作中,會出現(xiàn)讓多個請求同時返回結(jié)果的案例料身,這時如果用上面的方法會造成一定問題汤纸,因為上面異步的任務(wù)只是一個NSLog,如果是一個延時請求呢芹血,下面模擬一下多個請求同時發(fā)生的案例贮泞。
NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(4);NSLog(@"模擬請求1-----%@",[NSThread currentThread]);});});dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"模擬請求2-----%@",[NSThread currentThread]);});});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});
這里在dispatch_group_async這些個異步任務(wù)中,再次用異步的方式模擬了一個請求任務(wù)幔烛,下面看一下結(jié)果
2018-09-0416:50:50.221045+0800GCDDemo[76421:21817204]全部開始-----<NSThread:0x60000007ecc0>{number=1,name=main}2018-09-04 16:50:50.249321+0800 GCDDemo[76421:21817204] 全部結(jié)束-----<NSThread:0x60000007ecc0>{number=1,name=main}2018-09-04 16:50:53.225810+0800 GCDDemo[76421:21817394] 模擬請求2-----<NSThread:0x604000663000>{number=5,name=(null)}2018-09-04 16:50:54.225917+0800 GCDDemo[76421:21817393] 模擬請求1-----<NSThread:0x6040004649c0>{number=3,name=(null)}
造成這樣的原因是發(fā)起請求的兩個任務(wù)已經(jīng)完成了啃擦,所以調(diào)用了notify方法,但是請求的結(jié)果還沒有成功饿悬,所以這樣的代碼是有問題的令蛉,那么這里就使用dispatch_group_enter和dispatch_group_leave這對方法來解決。
NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_group_async(group,queue,^{dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(0,0),^{sleep(4);NSLog(@"模擬請求1-----%@",[NSThread currentThread]);dispatch_group_leave(group);});});dispatch_group_async(group,queue,^{dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"模擬請求2-----%@",[NSThread currentThread]);dispatch_group_leave(group);});});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});
這樣的結(jié)果就沒有問題了狡恬,這對方法可以理解為retain和release珠叔,當(dāng)每次進入到一個新的異步任務(wù)中時,使用dispatch_group_enter告訴原本的異步任務(wù)這里還沒有結(jié)束弟劲,當(dāng)請求完成之后使用dispatch_group_leave表示已經(jīng)結(jié)束了祷安,可以退出這個異步操作了,這樣的話兔乞,才能保證真正完成了一個延時函數(shù)汇鞭,結(jié)果如下
2018-09-0416:54:22.832901+0800GCDDemo[76462:21821447]全部開始-----<NSThread:0x60000007e980>{number=1,name=main}2018-09-04 16:54:25.837875+0800 GCDDemo[76462:21821502] 模擬請求2-----<NSThread:0x604000460840>{number=3,name=(null)}2018-09-04 16:54:26.833634+0800 GCDDemo[76462:21821503] 模擬請求1-----<NSThread:0x6000004676c0>{number=4,name=(null)}2018-09-04 16:54:26.833992+0800 GCDDemo[76462:21821447] 全部結(jié)束-----<NSThread:0x60000007e980>{number=1,name=main}
當(dāng)前案例還有其他的解決方案撇眯,我準(zhǔn)備在下一篇文章中將案例的其他解決辦法寫出來,這里不加贅述了虱咧。
dispatch_barrier_(a)sync
再次通過一個案例來認(rèn)識這個函數(shù),假設(shè)有a锚国,b腕巡,c,d四個異步任務(wù)血筑,我新增加了一個任務(wù)new绘沉,我希望可以在a和b之后完成并且在c和d之前完成,這里可以通過同步等其他方式解決豺总,不過有個更好的方式解決這個問題车伞,dispatch_barrier_async,和它的名字一樣喻喳,可以理解為一個柵欄另玖,將前后分隔開,在下先上代碼
dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue,^{sleep(3);NSLog(@"task - A");});dispatch_async(queue,^{sleep(2);NSLog(@"task - B");});NSLog(@"before barrier");dispatch_barrier_sync(queue,^{NSLog(@"task - new");});NSLog(@"after barrier");dispatch_async(queue,^{sleep(2);NSLog(@"task - C");});dispatch_async(queue,^{sleep(1);NSLog(@"task - D");});
這里模擬了四個異步操作任務(wù)表伦,將中間位置插入了一個new任務(wù)谦去,這里看看返回結(jié)果
2018-09-04 17:55:08.783989+0800 GCDDemo[76920:21886183] before barrier2018-09-04 17:55:10.787647+0800 GCDDemo[76920:21886310] task - B2018-09-04 17:55:11.785806+0800 GCDDemo[76920:21886309] task - A2018-09-04 17:55:11.786003+0800 GCDDemo[76920:21886183] task - new2018-09-04 17:55:11.786164+0800 GCDDemo[76920:21886183] after barrier2018-09-04 17:55:12.790452+0800 GCDDemo[76920:21886312] task - D2018-09-04 17:55:13.790432+0800 GCDDemo[76920:21886309] task - C
這里可以看出,由于有模擬延時操作蹦哼,所以before首先輸出鳄哭,然后執(zhí)行了A和B兩個任務(wù),而barrier中的任務(wù)并沒有延時卻在A和B之后纲熏,說明柵欄功能生效妆丘,有因為是sync同步的關(guān)系,所以后面的after緊接著輸出局劲,這里把sync換成async會有什么結(jié)果呢
2018-09-04 17:59:29.114406+0800 BlockDemo[76948:21890748] before barrier2018-09-04 17:59:29.114600+0800 BlockDemo[76948:21890748] after barrier
before和after是緊挨著輸出的勺拣,說明dispatch_barrier_sync同樣有同步的作用,而dispatch_barrier_async也有著異步的效果容握。
dispatch_apply
如果有一個案例需要按指定次數(shù)執(zhí)行g(shù)cd的任務(wù)宣脉,可以用這個函數(shù),第一個參數(shù)為次數(shù)剔氏,第二個參數(shù)為指定的隊列塑猖,第三個參數(shù)是帶參數(shù)的block,參數(shù)為當(dāng)前次數(shù)下標(biāo)
NSArray*array=@[@"1",@"2",@"3",@"4",@"5",];dispatch_apply([array count],dispatch_get_global_queue(0,0),^(size_t index){NSLog(@"元素:%@----第%ld次",array[index],index);});
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942336] 元素:4----第3次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942205] 元素:1----第0次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942337] 元素:3----第2次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942335] 元素:2----第1次2018-09-04 18:44:56.970380+0800 GCDDemo[77406:21942205] 元素:5----第4次
dispatch_semaphore
前文已經(jīng)有講部分關(guān)于產(chǎn)生不一樣數(shù)據(jù)的處理問題谈跛,不過有時需要更細(xì)致的排他控制羊苟,例如你到了一個屋子,里面有一個椅子感憾,你可以坐下蜡励,如果再拿來一個椅子,依然可以坐下,但是如果把椅子拿走凉倚,那么只能站著等待了兼都,semaphore使用信號量的方式實現(xiàn)了類似的情況,首先第一個函數(shù):
dispatch_semaphore_create(0);
這里創(chuàng)建了一個semaphore稽寒,返回類型是dispatch_semaphore_t扮碧,有一個參數(shù),表示初始信號量的值杏糙,這里給0,如果遇到wait則需要等待赖淤。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);dispatch_semaphore_signal(semaphore);
當(dāng)?shù)谝粋€函數(shù)中的信號量0時,這個函數(shù)需要進行等待咱旱,如果大于0莽龟,則將信號量-1锨天,第二個參數(shù)為等待的時間,這個可以根據(jù)需求使用搂赋,這里假定需要等待無限長的時間脑奠,知道信號量增加未知
第二個函數(shù)中會給信號量+1幅慌,可以看出這兩個函數(shù)是要成對出現(xiàn)的胰伍,如果單獨出現(xiàn)wait而且初始化為0的話骂租,會造成后續(xù)任務(wù)全部卡住無法執(zhí)行渗饮。下面給一個小的案例
dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_semaphore_t semaphore=dispatch_semaphore_create(0);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"完成1");dispatch_semaphore_signal(semaphore);});});dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(2);NSLog(@"完成2");dispatch_semaphore_signal(semaphore);});});dispatch_group_notify(group,queue,^{dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);NSLog(@"全部完成");});
這里再次模擬了多個請求需要同時返回結(jié)果的問題,通過信號量的方式私蕾,使用信號量的方式同樣可以完成這個需求踩叭,當(dāng)notify中有兩個等待信號的時候,只能通過請求成功的信號量增加的方法去抵消,當(dāng)兩個請求全部完成的時候嗤疯,等待信號也全部結(jié)束茂缚,這時表示任務(wù)全部完成脚囊。
不過如果更換一下需求會怎么樣呢桐磁,如果我們需要讓多個請求同步執(zhí)行要怎么做我擂,首先我們需要開啟異步去管理校摩,同樣請求也是異步方法,所以我們用這種方式不能讓請求同步執(zhí)行互妓,這里需要使用線程依賴的方式操作冯勉,GCD線程依賴這里面也使用semaphore的方式去做珠闰,修改代碼如下
dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_semaphore_t semaphore0=dispatch_semaphore_create(0);dispatch_semaphore_t semaphore1=dispatch_semaphore_create(0);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{[NSThread sleepForTimeInterval:3];NSLog(@"完成1");dispatch_semaphore_signal(semaphore0);});});dispatch_group_async(group,queue,^{dispatch_semaphore_wait(semaphore0,DISPATCH_TIME_FOREVER);dispatch_async(dispatch_get_global_queue(0,0),^{[NSThread sleepForTimeInterval:2];NSLog(@"完成2");dispatch_semaphore_signal(semaphore1);});});dispatch_group_notify(group,queue,^{dispatch_semaphore_wait(semaphore1,DISPATCH_TIME_FOREVER);NSLog(@"全部完成");});
這里創(chuàng)建了兩個信號量伏嗜,因為任務(wù)1模擬了時間更久承绸,我們這里需要讓任務(wù)1先完成,那么我們在任務(wù)2中把任務(wù)1的信號量等待轩猩,直到任務(wù)1完成并增加信號量均践,再執(zhí)行任務(wù)2摩幔,結(jié)果如下
2018-09-05 10:27:02.725024+0800 GCDDemo[81615:22654482] 完成12018-09-05 10:27:04.727478+0800 GCDDemo[81615:22654482] 完成22018-09-05 10:27:04.727852+0800 GCDDemo[81615:22654485] 全部完成
可以看出我們的目的已經(jīng)實現(xiàn)了或衡,但是如果有多個請求任務(wù)呢封断,必然需要創(chuàng)建多個信號量,按照需要的順序進行依賴彬呻,但是這種方法其實寫起來容易亂废岂,最好的方式是使用NSOperationQueue添加線程依賴湖苞,但是這里不多加贅述财骨,同樣后續(xù)文章中會詳細(xì)分析一次此案例隆箩,并使用其他方式解決這個問題羔杨。
dispatch_once
這個函數(shù)應(yīng)該也不會陌生兜材,當(dāng)我們希望只會執(zhí)行一次的函數(shù)我們會使用dispatch_once,所有我們創(chuàng)建單例的時候也會使用到這個函數(shù)寇荧。
+(instancetype)shareInstance{staticManager*manager=nil;staticdispatch_once_t onceToken;dispatch_once(&onceToken,^{manager=[Manager new];});returnmanager;}
這種單例不需要擔(dān)心線程問題揩抡,即使是多線程環(huán)境下也一定是安全的峦嗤,onceToken會保證運行過程中這部分只會執(zhí)行一次寻仗。
GCD的實現(xiàn)
GCD的使用是十分方便的,這里探討一下它是如何實現(xiàn)的
C語言實現(xiàn)的用于管理追加Block的FIFO隊列
用于排他控制的atomic類型的輕量級信號
C語言實現(xiàn)的管理線程的容器
首先確定一下用于實現(xiàn)dispatch queue的軟件組件
組件和技術(shù)
我們所使用的全部api都處于libdispatch庫中亚侠,Dispatch Queue通過結(jié)構(gòu)體和鏈表實現(xiàn)FIFO隊列硝烂。FIFO通過dispatch_async等函數(shù)管理添加的block滞谢。
但是block并不是直接加入到FIFO隊列中除抛,而是先加入Dispatch Continuation這個dispatch_continuation_t類型的結(jié)構(gòu)體中,再加入到隊列中橄教,這個結(jié)構(gòu)體包含了block所屬的group等信息护蝶,也就是執(zhí)行上下文翩迈。
XNU內(nèi)核有4中workqueue持灰,優(yōu)先級和Global Dispatch Queue的優(yōu)先級相同,當(dāng)在Global Dispatch Queue中執(zhí)行block時负饲,libdispatch從FIFO隊列中取出Dispatch Continuation堤魁,調(diào)用pthread_workqueue_additem_np函數(shù)喂链,將自身信息,符合其優(yōu)先級的workqueue信息以及執(zhí)行回調(diào)函數(shù)等傳遞給參數(shù)姨涡。
pthread_work_queue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用衩藤。通知workqueue應(yīng)當(dāng)執(zhí)行的項目。根據(jù)通知涛漂,XNU內(nèi)核基于系統(tǒng)判斷是否生成線程赏表,如果是overcommit屬性則始終生成線程。
workqueue的線程執(zhí)行pthread_workqueue函數(shù)瓢剿,該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)火架,在回調(diào)函數(shù)中執(zhí)行加入到Dispatch Continuation的Block。
Block執(zhí)行結(jié)束后淆游,進行通知Dispatch Group結(jié)束腊脱、釋放Dispatch Continuation等處理俱笛。開始準(zhǔn)備執(zhí)行Global Dispatch Queue的下一個Block
以上就是Dispatch Queue的大致執(zhí)行過程。
Dispatch Source
GCD中除了常用的Dispatch Queue外簸呈,還有Dispatch Source贩幻,它具有很多種類型處理能力,最常見的就是使用定時器。
dispatch_source_t timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,dispatch_get_main_queue());//創(chuàng)建一個dispatch_source_t類型變量,類型指定為定時器dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC,0);//指定定時器執(zhí)行時間為每秒執(zhí)行一次dispatch_source_set_event_handler(timer,^{NSLog(@"定時內(nèi)容");//每秒執(zhí)行的內(nèi)容});dispatch_source_set_cancel_handler(timer,^{NSLog(@"定時取消");//取消定時器的回調(diào)});dispatch_resume(timer);//啟動定時器//dispatch_source_cancel(timer);//取消定時器
這里本身會存在一個問題,就是set_event_handle這個回調(diào)會不執(zhí)行玲昧,原因是當(dāng)執(zhí)行過作用域之后亲配,這個source可能會被釋放掉犬钢,所以可以使用添加到屬性的方式放大source的作用域歹颓,保證定時器可以始終執(zhí)行
@property(nonatomic,strong)dispatch_source_t timer;
這里如果內(nèi)存管理語義使用了assign創(chuàng)建定時器,則會報出會被釋放的錯誤。
使用Dispatch Queue本身是不具備“取消”功能的,要么放棄取消,要么使用NSOperationQueue等方法,而Dispatch Source具備該功能,而且取消后執(zhí)行的處理可以使用block形式牵寺,這里也能看出Dispatch Source的強大功能。
到此為止關(guān)于GCD的這篇文章就結(jié)束了,如果文章中出現(xiàn)問題歡迎指出,并且如果有更優(yōu)秀的看法也歡迎提出或討論。
本文部分內(nèi)容參考自《Objective-C高級編程》一書狸眼,有興趣的小伙伴可以翻看一下