iOS開(kāi)發(fā)多線程篇之GCD

1771779-da221054beb5cbb4.png
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_suspenddispatch_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_enterdispatch_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");   
 });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纵诞,隨后出現(xiàn)的幾起案子上祈,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件登刺,死亡現(xiàn)場(chǎng)離奇詭異籽腕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)纸俭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)皇耗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人揍很,你說(shuō)我怎么就攤上這事郎楼。” “怎么了窒悔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵呜袁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我简珠,道長(zhǎng)阶界,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任聋庵,我火速辦了婚禮膘融,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祭玉。我一直安慰自己氧映,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布脱货。 她就那樣靜靜地躺著岛都,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹭劈。 梳的紋絲不亂的頭發(fā)上疗绣,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音铺韧,去河邊找鬼多矮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哈打,可吹牛的內(nèi)容都是我干的塔逃。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼料仗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼湾盗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起立轧,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤格粪,失蹤者是張志新(化名)和其女友劉穎躏吊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體帐萎,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡比伏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疆导。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赁项。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖澈段,靈堂內(nèi)的尸體忽然破棺而出悠菜,到底是詐尸還是另有隱情,我是刑警寧澤败富,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布悔醋,位于F島的核電站,受9級(jí)特大地震影響囤耳,放射性物質(zhì)發(fā)生泄漏篙顺。R本人自食惡果不足惜偶芍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一充择、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匪蟀,春花似錦椎麦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至段化,卻和暖如春嘁捷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背显熏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工雄嚣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喘蟆。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓缓升,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蕴轨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子港谊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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