前言
這是關(guān)于GCD的第二篇文章晤碘,GCD的API有100多個晴及,通過快捷鍵Option + 單擊虚倒,可以在Reference中的Grand Central Dispatch (GCD) Reference中看到诊笤。除了上篇文章介紹的幾個外系谐,其他用到的API就在這篇文章里記錄。
API 匯總記錄
1.dispatch_once
Execute a block once and only once.執(zhí)行一個block一次讨跟,且僅執(zhí)行一次纪他。
利用這個API,我們可以很方便的寫單例晾匠。
static HLTestObject *instance = nil;
+(instancetype)sharedInstance{staticdispatch_once_t onceToken;? ?
dispatch_once(&onceToken, ^{instance= [[[self class] alloc] init];? ? });returninstance;}
需要注意的是instance 和onceToken一定要保證是全局變量茶袒,用static修飾是最好的方案。
完整的關(guān)于單例的寫法和注意事項可以看這里iOS中的單例你用對了么凉馆?
2.dispatch_after
Schedule a block for execution on a given queue at a specified time
在指定的queue上特殊的時間執(zhí)行某個block片段
關(guān)于dispatch_time薪寓,第一個參數(shù)通常使用DISPATCH_TIME_NOW,它是一個表示
dispatch_time_t 的宏澜共,表示從現(xiàn)在開始算起向叉,第二個參數(shù)是第一個參數(shù)之后經(jīng)歷的時長。而我們通常用的NSEC_PER_SEC也是一個宏嗦董,還有其他的宏:
#defineNSEC_PER_SEC 1000000000ull//每秒有多少納秒
#defineNSEC_PER_MSEC 1000000ull//每毫秒有多少納秒
#defineUSEC_PER_SEC 1000000ull// 每秒有多少微秒
#defineNSEC_PER_USEC 1000ull// 每微秒有多少納秒
因為1秒鐘有NSEC_PER_SEC(也即1000000000)納秒母谎,所以NSEC_PER_SEC其實就相當(dāng)于1秒,那么兩秒就是2NSEC_PER_SEC京革;
同理奇唤,
NSEC_PER_MSEC就相當(dāng)于是1毫秒,那么2秒鐘匹摇,就應(yīng)該是2000NSEC_PER_MSEC冻记;
USEC_PER_SEC也相當(dāng)于1毫秒,2秒鐘就是2000USEC_PER_SEC来惧;
NSEC_PER_USEC相當(dāng)于 1微妙冗栗,所以要表示1秒鐘就是1000000NSEC_PER_USEC。
所以1秒后執(zhí)行某段代碼可以這樣寫:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{? ? ? ? NSLog(@"哈哈");? ? });? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{? ? ? ? NSLog(@"嘿嘿");});dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000* USEC_PER_SEC)), dispatch_get_main_queue(), ^{? ? ? ? NSLog(@"嗯嗯");? ? });? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000000 * NSEC_PER_USEC)), dispatch_get_main_queue(), ^{? ? ? ? NSLog(@"呃呃");});
這個API的作用與下面這個方法類似:
[selfperformSelector:@selector(testClick:)withObject:nilafterDelay:2.0];
3.dispatch_group
關(guān)于dispatch_group的API有好幾個供搀,相關(guān)API的使用場景是:在多個異步任務(wù)全部執(zhí)行完畢后隅居,執(zhí)行某個任務(wù)。如果用同步任務(wù)或串行隊列葛虐,就沒有意義了胎源,要謹(jǐn)記。
這里有兩種實現(xiàn)方式:
方式一
利用dispatch_group_async和dispatch_group_notify配合屿脐,關(guān)鍵代碼:
dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(0,0);for (inti =0; i < 5; i++) {dispatch_group_async(group, queue, ^{? ? ? ? ? ? NSLog(@"并發(fā)%d----線程:%@", i,[NSThread currentThread]);[NSThread sleepForTimeInterval:i];});}? ? dispatch_group_notify(group, queue, ^{? ? ? ? NSLog(@"dispatch_group_notify---%@",[NSThread currentThread]);});// 打印結(jié)果:2016-07-0818:00:22.795PractiseProject[10437:231800] 并發(fā)0----線程:{number =2, name = (null)}2016-07-0818:00:22.795PractiseProject[10437:231815] 并發(fā)1----線程:{number =3, name = (null)}2016-07-0818:00:22.795PractiseProject[10437:231821] 并發(fā)3----線程:{number =4, name = (null)}2016-07-0818:00:22.795PractiseProject[10437:231800] 并發(fā)4----線程:{number =2, name = (null)}2016-07-0818:00:22.795PractiseProject[10437:231807] 并發(fā)2----線程:{number =5, name = (null)}2016-07-0818:00:26.799PractiseProject[10437:231821] dispatch_group_notify---{number =4, name = (null)}
方式二
利用dispatch_group_enter涕蚤、dispatch_group_leave和dispatch_group_notify配合宪卿,其中需要注意的是有dispatch_group_enter就必定有一個dispatch_group_leave與之對應(yīng),否則可能會出現(xiàn)令你意想不到的崩潰万栅。
關(guān)鍵代碼:
dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(0,0);for (inti =0; i < 5; i++) {dispatch_group_enter(group);dispatch_async(queue, ^{? ? ? ? ? ? NSLog(@"并發(fā)%d----線程:%@", i,[NSThread currentThread]);[NSThread sleepForTimeInterval:i];dispatch_group_leave(group);});}? ? dispatch_group_notify(group, queue, ^{? ? ? ? NSLog(@"dispatch_group_notify---%@",[NSThread currentThread]);});// 打印結(jié)果:2016-07-1110:57:56.697PractiseProject[1859:76390] 并行1----線程:{number =2, name = (null)}2016-07-1110:57:56.697PractiseProject[1859:76421] 并行2----線程:{number =3, name = (null)}2016-07-1110:57:56.697PractiseProject[1859:76399] 并行0----線程:{number =5, name = (null)}2016-07-1110:57:56.697PractiseProject[1859:76436] 并行3----線程:{number =4, name = (null)}2016-07-1110:57:56.697PractiseProject[1859:76437] 并行4----線程:{number =6, name = (null)}2016-07-1110:58:00.702PractiseProject[1859:76436] dispatch_group_notify---{number =4, name = (null)}
4.dispatch_barrier
dispatch_barrier分為同步dispatch_barrier_sync和異步dispatch_barrier_async兩種情況佑钾。dispatch_barrier的功能其實跟上面標(biāo)題3的場景比較類似,它可以保證在dispatch_barrier前提交的任務(wù)執(zhí)行完后烦粒,再執(zhí)行dispatch_barrier中的任務(wù)休溶,等dispatch_barrier中的任務(wù)執(zhí)行完后,才繼續(xù)執(zhí)行在dispatch_barrier之后提交的任務(wù)扰她。
4.1 dispatch_barrier_async
首先兽掰,介紹一下異步dispatch_barrier_async,它會在新線程中執(zhí)行任務(wù)徒役,在蘋果官方的描述中是這么寫的:
11.png
大致意思是:如果我們用dispatch_queue_create創(chuàng)建的并發(fā)隊列上孽尽,使用dispatch_barrier_async,那么在dispatch_barrier_async中的任務(wù)會等在它之前提交的任務(wù)全部執(zhí)行完(之前的幾個任務(wù)哪個先執(zhí)行完依然是不確定的)后再執(zhí)行,而在它之后提交的任務(wù)忧勿,會等dispatch_barrier_async中的任務(wù)執(zhí)行完之后杉女,才會開始執(zhí)行。但是如果使用串行隊列或者dispatch_get_global_queue創(chuàng)建的并發(fā)隊列狐蜕,則dispatch_barrier_async的功能就類似dispatch_async,可以將dispatch_barrier_async直接替換成dispatch_async卸夕,效果一樣层释。
一個使用dispatch_barrier_async的示例代碼:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);for (inti =0; i < 5; i++) {dispatch_async(queue, ^{? ? ? ? ? ? NSLog(@"并發(fā)%d----線程:%@", i,[NSThread currentThread]);[NSThread sleepForTimeInterval:i];});if (i==2) {? ? ? ? ? ? dispatch_barrier_async(queue, ^{? ? ? ? ? ? ? ? NSLog(@"barrier----%@",[NSThread currentThread]);});}? ? }// 打印結(jié)果:2016-07-1112:45:36.173PractiseProject[2579:110181] 并發(fā)2----線程:{number =3, name = (null)}2016-07-1112:45:36.173PractiseProject[2579:110175] 并發(fā)1----線程:{number =4, name = (null)}2016-07-1112:45:36.173PractiseProject[2579:110166] 并發(fā)0----線程:{number =2, name = (null)}2016-07-1112:45:38.177PractiseProject[2579:110181] barrier----{number =3, name = (null)}2016-07-1112:45:38.177PractiseProject[2579:110175] 并發(fā)4----線程:{number =4, name = (null)}2016-07-1112:45:38.177PractiseProject[2579:110181] 并發(fā)3----線程:{number =3, name = (null)}
接下來介紹dispatch_barrier_async的使用場景,我們都知道關(guān)于數(shù)據(jù)的讀寫安全是非常麻煩的,必須保證在寫入數(shù)據(jù)的時候快集,不能讀取數(shù)據(jù)贡羔;而寫入完成后,不管有多少個線程在讀取數(shù)據(jù)都是OK的个初。這個場景使用dispatch_barrier_async再合適不過了乖寒。
如何操作呢?
重寫某個數(shù)據(jù)的set 和get 方法:
- (void)setHeight:(int)height{? ? dispatch_barrier_async(_conCorrentQueue, ^{? ? ? ? _height =height;? ? });}- (int)height{? ? __blockintheight;? ? dispatch_sync(_conCorrentQueue, ^{height= _height;? ? });returnheight;}
注意:1.從Xcode 4開始院溺,我們定義property后楣嘁,編譯器會自動幫我們添加@synthesize,但是如果我們同時重寫setter和getter,那么編譯器便不再幫我們添加@synthesize珍逸,我們需要自己添加@synthesize逐虚。
2.dispatch_barrier_async只能使用dispatch_queue_create創(chuàng)建的并發(fā)隊列,才能正確發(fā)揮它的作用谆膳。
4.2 dispatch_barrier_sync
dispatch_barrier_sync與dispatch_barrier_async的功能基本一致叭爱,不同之處是,dispatch_barrier_sync是在當(dāng)前線程中執(zhí)行block中的任務(wù)漱病,而dispatch_barrier_async則是在新的線程(有可能是之前使用過的子線程)中執(zhí)行任務(wù)买雾。 它們都是在用dispatch_queue_create創(chuàng)建的并發(fā)隊列上有效果把曼,而在串行隊列或者dispatch_get_global_queue創(chuàng)建的并發(fā)隊列中,作用與dispatch_sync一致漓穿。
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);for (inti =0; i < 5; i++) {dispatch_async(queue, ^{? ? ? ? ? ? [NSThread sleepForTimeInterval:2];NSLog(@"并發(fā)%d----線程:%@", i,[NSThread currentThread]);});if (i==2) {? ? ? ? ? ? dispatch_barrier_sync(queue, ^{? ? ? ? ? ? ? ? NSLog(@"barrier----%@",[NSThread currentThread]);});}? ? }// 打印結(jié)果:2016-07-1113:27:19.139PractiseProject[2820:122236] 并發(fā)0----線程:{number =4, name = (null)}2016-07-1113:27:19.139PractiseProject[2820:122229] 并發(fā)1----線程:{number =3, name = (null)}2016-07-1113:27:19.139PractiseProject[2820:122322] 并發(fā)2----線程:{number =2, name = (null)}2016-07-1113:27:19.140PractiseProject[2820:122192] barrier----{number =1, name = main}2016-07-1113:27:21.143PractiseProject[2820:122322] 并發(fā)4----線程:{number =2, name = (null)}2016-07-1113:27:21.143PractiseProject[2820:122229] 并發(fā)3----線程:{number =3, name = (null)}
dispatch_barrier決定的只是它的任務(wù)是否在新的線程中執(zhí)行嗤军,以及它一定在前面幾個任務(wù)執(zhí)行完后執(zhí)行,并不會影響之前任務(wù)的執(zhí)行順序等器净。
在串行隊列或者dispatch_get_global_queue創(chuàng)建的并發(fā)隊列中型雳,dispatch_barrier_sync僅僅相當(dāng)于dispatch_sync。
5.Queue-Specific
由于dispatch_get_current_queueAPI的移除山害,為了能夠判斷當(dāng)前queue是否是之前創(chuàng)建的queue纠俭,我們可以利用dispatch_queue_set_specific和dispatch_get_specific給queue關(guān)聯(lián)一個context data,后面再利用這個標(biāo)識獲取到context data浪慌。如果可以獲取到說明當(dāng)前上下文是在自己創(chuàng)建的queue中冤荆,如果不能獲取到context data則表示當(dāng)前是在其他隊列上。
使用場景: 自己創(chuàng)建一個隊列权纤,然后保證所有的操作都在該隊列上執(zhí)行钓简。XMPP中有比較多的dispatch_queue_set_specific和dispatch_get_specific使用案例。
設(shè)置標(biāo)識和關(guān)聯(lián)的數(shù)據(jù):
dispatch_queue_tqueue= dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);constvoid*queueSpecificKey = @"queueSpecificKey";dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey,NULL);
獲取關(guān)聯(lián)數(shù)據(jù):dispatch_get_specific(queueSpecificKey)
完整的示例:
dispatch_queue_tqueue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);// 當(dāng)然這里也可以是其他類型的隊列//? ? dispatch_queue_t queue = dispatch_get_global_queue(0, 0);//? ? dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);constvoid*queueSpecificKey =@"queueSpecificKey";? ? dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey,NULL);dispatch_async(queue, ^{NSLog(@"異步任務(wù)");if(dispatch_get_specific(queueSpecificKey)) {NSLog(@"com.haley.cn---1隊列");? ? ? ? }else{NSLog(@"---1其他隊列");? ? ? ? }? ? });NSLog(@"主線程汹想,主隊列");if(dispatch_get_specific(queueSpecificKey)) {NSLog(@"com.haley.cn---2隊列");? ? }else{NSLog(@"----2其他隊列");? ? }// 打印結(jié)果:2016-07-1114:30:56.772PractiseProject[3379:152363] 主線程外邓,主隊列2016-07-1114:30:56.772PractiseProject[3379:152363] ----2其他隊列2016-07-1114:30:56.772PractiseProject[3379:152451] 異步任務(wù)2016-07-1114:30:56.773PractiseProject[3379:152451] com.haley.cn---1隊列
dispatch_get_specific所處的環(huán)境如果是在目標(biāo)對列上時,就可以獲取到關(guān)聯(lián)的數(shù)據(jù)古掏,否則就無法獲取關(guān)聯(lián)數(shù)據(jù)损话,返回NULL。
看一看XMPP中的使用案例:
- (BOOL)activate:(XMPPStream*)aXmppStream{ __blockBOOLresult = YES;dispatch_block_tblock= ^{? if (xmppStream != nil)? {? result = NO;}? else? {? xmppStream = aXmppStream;[xmppStreamaddDelegate:selfdelegateQueue:moduleQueue];[xmppStream registerModule:self];} };if (dispatch_get_specific(moduleQueueTag))block();elsedispatch_sync(moduleQueue,block);return result;}
為了保證block是在目標(biāo)隊列上執(zhí)行槽唾,先判斷當(dāng)前是否在目標(biāo)隊列上(如果能取到關(guān)聯(lián)數(shù)據(jù)丧枪,則說明在當(dāng)前隊列上),如果在目標(biāo)隊列上庞萍,直接執(zhí)行block拧烦,否則就在目標(biāo)隊列上同步執(zhí)行。