dispatch_once_t必須是全局或static變量
這一條算是“老生常談”了桃纯,但我認(rèn)為還是有必要強(qiáng)調(diào)一次嚷那,畢竟非全局或非static的dispatch_once_t變量在使用時(shí)會(huì)導(dǎo)致非常不好排查的bug芜赌,正確的如下:
//靜態(tài)變量,保證只有一份實(shí)例,才能確保只執(zhí)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//單例代碼
});
其實(shí)就是保證dispatch_once_t只有一份實(shí)例。
dispatch_queue_create的第二個(gè)參數(shù)
dispatch_queue_create青伤,創(chuàng)建隊(duì)列用的,它的參數(shù)只有兩個(gè)殴瘦,原型如下:
dispatch_queue_t dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr );
在網(wǎng)上的大部分教程里(甚至Apple自己的文檔里)狠角,都是這么創(chuàng)建串行隊(duì)列的:
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
看,第二個(gè)參數(shù)傳的是“NULL”蚪腋。 但是dispatch_queue_attr_t類(lèi)型是有已經(jīng)定義好的常量的丰歌,所以我認(rèn)為,為了更加的清晰屉凯、嚴(yán)謹(jǐn)立帖,最好如下創(chuàng)建隊(duì)列:
//串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_SERIAL);
//并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_CONCURRENT);
常量就是為了使代碼更加“易懂”,更加清晰悠砚,既然有晓勇,為啥不用呢~
dispatch_after是延遲提交,不是延遲運(yùn)行
先看看官方文檔的說(shuō)明:
Enqueue a block for execution at the specified time.
Enqueue,就是入隊(duì)宵蕉,指的就是將一個(gè)Block在特定的延時(shí)以后酝静,加入到指定的隊(duì)列中节榜,不是在特定的時(shí)間后立即運(yùn)行羡玛!。
看看如下代碼示例:
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//立即打印一條信息
NSLog(@"Begin add block...");
//提交一個(gè)block
dispatch_async(queue, ^{
//Sleep 10秒
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});
//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"After...");
});
結(jié)果如下:
2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] First block done...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] After...
從結(jié)果也驗(yàn)證了宗苍,dispatch_after只是延時(shí)提交block稼稿,并不是延時(shí)后立即執(zhí)行。所以想用dispatch_after精確控制運(yùn)行狀態(tài)的朋友可要注意了~
正確創(chuàng)建dispatch_time_t
用dispatch_after的時(shí)候就會(huì)用到dispatch_time_t變量讳窟,但是如何創(chuàng)建合適的時(shí)間呢让歼?答案就是用dispatch_time函數(shù),其原型如下:
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
第一個(gè)參數(shù)一般是DISPATCH_TIME_NOW丽啡,表示從現(xiàn)在開(kāi)始谋右。那么第二個(gè)參數(shù)就是真正的延時(shí)的具體時(shí)間。
這里要特別注意的是补箍,delta參數(shù)是“納秒改执!”,就是說(shuō)坑雅,延時(shí)1秒的話辈挂,delta應(yīng)該是“1000000000”=。=裹粤,太長(zhǎng)了终蒂,所以理所當(dāng)然系統(tǒng)提供了常量,如下:
#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
關(guān)鍵詞解釋?zhuān)?/p>
- NSEC:納秒遥诉。
- USEC:微妙拇泣。
- SEC:秒
- PER:每
所以: - NSEC_PER_SEC,每秒有多少納秒矮锈。
- USEC_PER_SEC挫酿,每秒有多少毫秒。(注意是指在納秒的基礎(chǔ)上)
- NSEC_PER_USEC愕难,每毫秒有多少納秒早龟。
所以,延時(shí)1秒可以寫(xiě)成如下幾種:
dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
最后一個(gè)“USEC_PER_SEC * NSEC_PER_USEC”猫缭,翻譯過(guò)來(lái)就是“每秒的毫秒數(shù)乘以每毫秒的納秒數(shù)”葱弟,也就是“每秒的納秒數(shù)”,所以猜丹,延時(shí)500毫秒之類(lèi)的芝加,也就不難了吧~
dispatch_suspend != 立即停止隊(duì)列的運(yùn)行
dispatch_suspend,dispatch_resume提供了“掛起、恢復(fù)”隊(duì)列的功能藏杖,簡(jiǎn)單來(lái)說(shuō)将塑,就是可以暫停、恢復(fù)隊(duì)列上的任務(wù)蝌麸。但是這里的“掛起”点寥,并不能保證可以立即停止隊(duì)列上正在運(yùn)行的block,看如下例子:
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一個(gè)block来吩,延時(shí)5秒打印敢辩。
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds...");
});
//提交第二個(gè)block,也是延時(shí)5秒打印
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds again...");
});
//延時(shí)一秒NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];
//掛起隊(duì)列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延時(shí)10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];
//恢復(fù)隊(duì)列NSLog(@"resume...");
dispatch_resume(queue);
運(yùn)行結(jié)果如下:
2015-04-01 00:32:09.903 GCDTest[47201:1883834] sleep 1 second...
2015-04-01 00:32:10.910 GCDTest[47201:1883834] suspend...
2015-04-01 00:32:10.910 GCDTest[47201:1883834] sleep 10 second...2015-04-01 00:32:14.908 GCDTest[47201:1883856] After 5 seconds...
2015-04-01 00:32:20.911 GCDTest[47201:1883834] resume...
2015-04-01 00:32:25.912 GCDTest[47201:1883856] After 5 seconds again...
可知弟疆,在dispatch_suspend掛起隊(duì)列后戚长,第一個(gè)block還是在運(yùn)行,并且正常輸出怠苔。結(jié)合文檔同廉,我們可以得知,dispatch_suspend并不會(huì)立即暫停正在運(yùn)行的block柑司,而是在當(dāng)前block執(zhí)行完成后迫肖,暫停后續(xù)的block執(zhí)行。
所以下次想暫停正在隊(duì)列上運(yùn)行的block時(shí)帜羊,還是不要用dispatch_suspend了吧~
“同步”的dispatch_apply
dispatch_apply的作用是在一個(gè)隊(duì)列(串行或并行)上“運(yùn)行”多次block咒程,其實(shí)就是簡(jiǎn)化了用循環(huán)去向隊(duì)列依次添加block任務(wù)。但是我個(gè)人覺(jué)得這個(gè)函數(shù)就是個(gè)“坑”讼育,先看看如下代碼運(yùn)行結(jié)果:
//創(chuàng)建異步串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//運(yùn)行block3次
dispatch_apply(3, queue, ^(size_t i) {
NSLog(@"apply loop: %zu", i);
});
//打印信息
NSLog(@"After apply");
運(yùn)行的結(jié)果是:
2015-04-01 00:55:40.854 GCDTest[47402:1893289] apply loop: 0
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 1
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 2
2015-04-01 00:55:40.856 GCDTest[47402:1893289] After apply
看帐姻,明明是提交到異步的隊(duì)列去運(yùn)行,但是“After apply”居然在apply后打印奶段,也就是說(shuō)饥瓷,dispatch_apply將外面的線程(main線程)“阻塞”了!
查看官方文檔痹籍,dispatch_apply確實(shí)會(huì)“等待”其所有的循環(huán)運(yùn)行完畢才往下執(zhí)行=呢铆。=,看來(lái)要小心使用了蹲缠。
避免死鎖棺克!
dispatch_sync導(dǎo)致的死鎖
涉及到多線程的時(shí)候,不可避免的就會(huì)有“死鎖”這個(gè)問(wèn)題线定,在使用GCD時(shí)娜谊,往往一不小心,就可能造成死鎖斤讥,看看下面的“死鎖”例子:
//在main線程使用“同步”方法提交Block纱皆,必定會(huì)死鎖。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"I am block...");
});
你可能會(huì)說(shuō),這么低級(jí)的錯(cuò)誤派草,我怎么會(huì)犯搀缠,那么,看看下面的:
- (void)updateUI1 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Update ui 1");
//死鎖近迁!
[self updateUI2]; });
}
- (void)updateUI2 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Update ui 2"); });
}
在你不注意的時(shí)候艺普,嵌套調(diào)用可能就會(huì)造成死鎖!所以為了“世界和平”=钳踊。=衷敌,我們還是少用dispatch_sync吧勿侯。
靈活使用dispatch_group
很多時(shí)候我們需要等待一系列任務(wù)(block)執(zhí)行完成拓瞪,然后再做一些收尾的工作。如果是有序的任務(wù)助琐,可以分步驟完成的祭埂,直接使用串行隊(duì)列就行。但是如果是一系列并行執(zhí)行的任務(wù)呢兵钮?這個(gè)時(shí)候蛆橡,就需要dispatch_group幫忙了~總的來(lái)說(shuō),dispatch_group的使用分如下幾步:
- 創(chuàng)建dispatch_group_t
- 添加任務(wù)(block)
- 添加結(jié)束任務(wù)(如清理操作掘譬、通知UI等)
下面著重講講在后面兩步泰演。
添加任務(wù)
添加任務(wù)可以分為以下兩種情況:
1、自己創(chuàng)建隊(duì)列:使用dispatch_group_async葱轩。
2睦焕、無(wú)法直接使用隊(duì)列變量(如使用AFNetworking添加異步任務(wù)):使用dispatch_group_enter,dispatch_group_leave靴拱。
自己創(chuàng)建隊(duì)列時(shí)垃喊,當(dāng)然就用dispatch_group_async函數(shù),簡(jiǎn)單有效袜炕,簡(jiǎn)單例子如下:
dispatch_group_t group = dispatch_group_create();
//創(chuàng)建并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//Do you work...
});
當(dāng)你無(wú)法直接使用隊(duì)列變量時(shí)本谜,就無(wú)法使用dispatch_group_async了,下面以使用AFNetworking時(shí)的情況:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//Enter group
dispatch_group_enter(group);
[manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Deal with result...
//Leave group
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//Deal with error...
//Leave group
dispatch_group_leave(group);
}];
使用dispatch_group_enter偎窘,dispatch_group_leave就可以方便的將一系列網(wǎng)絡(luò)請(qǐng)求“打包”起來(lái)~
添加結(jié)束任務(wù)
添加結(jié)束任務(wù)也可以分為兩種情況乌助,如下:
1、在當(dāng)前線程阻塞的同步等待:dispatch_group_wait陌知。
//(2)監(jiān)視函數(shù) dispatch_group_wait
// 等待 time 時(shí)間后 對(duì)隊(duì)列進(jìn)行監(jiān)聽(tīng)
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 5ull *NSEC_PER_SEC);
long result=dispatch_group_wait(group, time);
if (result ==0) {
NSLog(@"finish");
}else {
NSLog(@"not finish");
}
2他托、添加一個(gè)異步執(zhí)行的任務(wù)作為結(jié)束任務(wù):dispatch_group_notify
//(1) 監(jiān)視函數(shù) dispatch_group_notify
//隊(duì)列操作執(zhí)行結(jié)束
dispatch_group_notify(group, queue, ^{
//執(zhí)行操作
NSLog(@"done");
});