iOS多線程 - GCD 詳解

前言


嘿嘿嘿,精品。


之前寫(xiě)了一篇iOS多線程匯總排监,地址如下
http://www.reibang.com/p/2642138d6bef
這里對(duì)GCD進(jìn)行詳細(xì)補(bǔ)充

概述


全稱(chēng)是Grand Central Dispatch延蟹,可譯為“牛逼的中樞調(diào)度器”。
純C語(yǔ)言悼沈,提供了非常多強(qiáng)大的函數(shù)。

優(yōu)勢(shì)


GCD是蘋(píng)果公司為多核的并行運(yùn)算提出的解決方案
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程窿给、調(diào)度任務(wù)、銷(xiāo)毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù)率拒,不需要編寫(xiě)任何線程管理代碼

任務(wù)和隊(duì)列


GCD中有兩個(gè)核心概念

  • 任務(wù):執(zhí)行什么操作
  • 隊(duì)列:用來(lái)存放任務(wù)

使用GCD兩個(gè)步驟

  • 定制任務(wù)
    • 確定想做的事
  • 確定想做的事情,將任務(wù)添加到隊(duì)列中
    • GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出崩泡,放到對(duì)應(yīng)的線程中執(zhí)行。

GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出猬膨,放到對(duì)應(yīng)的線程中執(zhí)行
任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出角撞,后進(jìn)后出

執(zhí)行任務(wù)

  • GCD中有兩個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù)
    • 用同步的方式執(zhí)行任務(wù)
// queue:隊(duì)列
// block:任務(wù)

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • 用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • 同步和異步的區(qū)別
    • 同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力

    • 異步:可以在新的線程中執(zhí)行任務(wù)勃痴,具備開(kāi)啟新線程的能力

添加隊(duì)列

  • GCD的隊(duì)列可以分為兩大類(lèi)型

    • 并發(fā)隊(duì)列(Concurrent Dispatch Queue)

      • 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
      • 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
    • 串行隊(duì)列(Serial Dispatch Queue)

      • 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后谒所,再執(zhí)行下一個(gè)任務(wù))

易混術(shù)語(yǔ)

  • 有4個(gè)術(shù)語(yǔ)比較容易混淆:同步、異步沛申、并發(fā)劣领、串行

  • 同步和異步主要影響:能不能開(kāi)啟新的線程

    • 同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力

    • 異步:在新的線程中執(zhí)行任務(wù)铁材,具備開(kāi)啟新線程的能力

  • 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式

    • 并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行

    • 串行:一個(gè)任務(wù)執(zhí)行完畢后剖踊,再執(zhí)行下一個(gè)任務(wù)

并發(fā)隊(duì)列

  • GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列庶弃,供整個(gè)應(yīng)用使用,不需要手動(dòng)創(chuàng)建

  • 使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊(duì)列

dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊(duì)列的優(yōu)先級(jí)
unsigned long flags); // 此參數(shù)暫時(shí)無(wú)用德澈,用0即可
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 獲得全局并發(fā)隊(duì)列
  • 手動(dòng)創(chuàng)建并發(fā)隊(duì)列
    • 參數(shù)1:隊(duì)列標(biāo)識(shí)
    • 參數(shù)2:隊(duì)列類(lèi)型
dispatch_queue_t concurrentQueue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
  • 全局并發(fā)隊(duì)列的優(yōu)先級(jí)
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺(tái)

串行隊(duì)列

  • GCD中獲得串行有兩種途徑

    • 手動(dòng)創(chuàng)建串行隊(duì)列

    • 使用dispatch_queue_create函數(shù)創(chuàng)建串行隊(duì)列

// "SERIAL" 是一個(gè)標(biāo)識(shí)符歇攻,可以自己填寫(xiě),通常填寫(xiě)com.公司的域名
dispatch_queue_t serialQueue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
// 或者
dispatch_queue_t serialQueue = dispatch_queue_create("SERIAL", NULL);
dispatch_release(queue); // 非ARC需要釋放手動(dòng)創(chuàng)建的隊(duì)列


- 使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)

- 主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列

- 放在主隊(duì)列中的任務(wù)梆造,都會(huì)放到主線程中執(zhí)行

- 使用dispatch_get_main_queue()獲得主隊(duì)列

dispatch_queue_t queue = dispatch_get_main_queue();


####各種隊(duì)列的執(zhí)行效果

![](http://upload-images.jianshu.io/upload_images/2595997-971676ca92a8a6fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![](http://upload-images.jianshu.io/upload_images/2595997-cf291e35f32f7629.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 小知識(shí):同步函數(shù)立刻執(zhí)行缴守,異步函數(shù)會(huì)等待自身所存在方法結(jié)束后才執(zhí)行。

- 注意:
  - 使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù)镇辉,會(huì)卡住當(dāng)前的串行隊(duì)列,叫做死鎖(有些像把串行隊(duì)列嵌套)(下面會(huì)做詳細(xì)說(shuō)明).屡穗。

#死鎖
------
#### 搞清線程(Thread)和隊(duì)列(Queue)的區(qū)別
網(wǎng)上一些講解關(guān)于GCD死鎖的文章,有一些非常明顯的錯(cuò)誤忽肛,比如:認(rèn)為死鎖的原因是線程阻塞造成的村砂,這是非常大的誤解,GCD死鎖的原因是隊(duì)列阻塞屹逛,而不是線程阻塞础废!


![](http://upload-images.jianshu.io/upload_images/2595997-e0dbda73028aa1b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在開(kāi)發(fā)中,我們會(huì)把block罕模,也就是我們想做的任務(wù)评腺,交給GCD函數(shù)。GCD函數(shù)會(huì)把任務(wù)放進(jìn)我們指定的隊(duì)列(Queue)淑掌,當(dāng)然GCD函數(shù)內(nèi)部不止是把任務(wù)放進(jìn)隊(duì)列蒿讥,還包括一些其他不為我們所知的操作。隊(duì)列遵循嚴(yán)格的先進(jìn)先出原則抛腕,同一個(gè)Queue中芋绸,最早入列的block,會(huì)最早被分配給線程執(zhí)行担敌。系統(tǒng)(“系統(tǒng)”指所有被蘋(píng)果黑盒封裝摔敛,未公開(kāi)源碼,我們不能得知的操作柄错,下同)會(huì)依據(jù)順序從隊(duì)列中取出block舷夺,并且交由線程執(zhí)行苦酱。GCD隊(duì)列只是組織待執(zhí)行任務(wù)的一個(gè)數(shù)據(jù)結(jié)構(gòu)封裝售貌,而線程,才是執(zhí)行任務(wù)的人疫萤。

#### 程序執(zhí)行順序

要往下面講颂跨,不得不回顧一個(gè)再基礎(chǔ)不過(guò)的知識(shí)點(diǎn),我想扯饶,這是每一個(gè)程序員恒削,入門(mén)就知道的超級(jí)簡(jiǎn)單的知識(shí)池颈。雖然它非常基礎(chǔ)钓丰,但是躯砰,這正是造成我們GCD死鎖的重要因素。很多困難的問(wèn)題携丁,它們背后隱藏的東西往往非常簡(jiǎn)單琢歇,因?yàn)槭挛镉肋h(yuǎn)不會(huì)脫離本質(zhì)。

讓我們來(lái)看看下面的這個(gè)C程序:

include <stdio.h>

void printFiveNumbers(){
printf("開(kāi)始執(zhí)行printFiveNumbers函數(shù)了\n");
for (int i = 0; i < 5; i++) {
printf("printFiveNumbers - %d\n",i);
}
printf("執(zhí)行完printFiveNumbers函數(shù)了\n");
}

//main函數(shù)是程序的入口
int main(){
printf("main函數(shù)開(kāi)始執(zhí)行了\n");
printFiveNumbers();
printf("main函數(shù)執(zhí)行完了\n");
return 0;
}



![](http://upload-images.jianshu.io/upload_images/2595997-4579d283d6031841.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

大家都知道梦鉴,運(yùn)行的結(jié)果是怎么樣了李茫,程序的入口是main函數(shù),于是Run這個(gè)程序后肥橙,馬上就會(huì)進(jìn)入main函數(shù)執(zhí)行魄宏,執(zhí)行了第一句打印后,會(huì)跳入printFiveNumbers這個(gè)函數(shù)執(zhí)行存筏,直到printFiveNumbers執(zhí)行完宠互,才會(huì)返回到main函數(shù)繼續(xù)執(zhí)行下一句。重點(diǎn)是:外層方法會(huì)等待內(nèi)層方法返回后方篮,再執(zhí)行下一句指令名秀。就好像把printFiveNumbers函數(shù)的所有語(yǔ)句,都復(fù)制粘貼到了main方法里一樣藕溅。

#### GCD死鎖的本質(zhì)

讓我們看看下面這個(gè)程序:

override func viewDidLoad() {
super.viewDidLoad()
print("Start (NSThread.currentThread())")
//GCD同步函數(shù)
dispatch_sync(dispatch_get_main_queue(), {
for i in 0...100{
print("(i) (NSThread.currentThread())")
}
})
print("End (NSThread.currentThread())")
}


![](http://upload-images.jianshu.io/upload_images/2595997-3b6780b255265357.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


這個(gè)程序就是典型的死鎖匕得,可以看到,只打印了“Start”一行巾表,就再也沒(méi)有響應(yīng)了汁掠,已經(jīng)造成了GCD死鎖。為什么會(huì)這樣呢集币?讓我們來(lái)解讀一下這段程序的運(yùn)行順序:首先會(huì)打印“Start”考阱,然后將主隊(duì)列和一個(gè)block傳入GCD同步函數(shù)dispatch_sync中,等待sync函數(shù)執(zhí)行鞠苟,直到它返回乞榨,才會(huì)執(zhí)行打印“End”的語(yǔ)句〉庇椋可是吃既,竟然沒(méi)有反應(yīng)了?block中的101個(gè)數(shù)字沒(méi)有被打印出來(lái)任何一個(gè)跨细,viewDidLoad()中的End也沒(méi)有被打印出來(lái)鹦倚。也就是說(shuō),block沒(méi)有得到執(zhí)行的機(jī)會(huì)冀惭,viewDidLoad也沒(méi)有繼續(xù)執(zhí)行下去震叙。為什么block不執(zhí)行呢掀鹅?因?yàn)関iewDidLoad也是執(zhí)行在主隊(duì)列的,它是正在被執(zhí)行的任務(wù)媒楼,也就是說(shuō)乐尊,viewDidLoad()是主隊(duì)列的隊(duì)頭。主隊(duì)列是串行隊(duì)列划址,任務(wù)不能并發(fā)執(zhí)行科吭,同時(shí)只能有一個(gè)任務(wù)在執(zhí)行,也就是隊(duì)頭的任務(wù)才能被出列執(zhí)行猴鲫。我們現(xiàn)在被執(zhí)行的任務(wù)是viewDidLoad()对人,然后我們又將block入列到同一個(gè)隊(duì)列,它比viewDidLoad()后入列拂共,遵循先進(jìn)先出的原理牺弄,它必須等到viewDidLoad()執(zhí)行完,才能被執(zhí)行宜狐。但是势告,dispatch_sync函數(shù)的特性是,等待block被執(zhí)行完畢抚恒,才會(huì)返回咱台,因此,只要block一天不被執(zhí)行俭驮,它就一天不返回回溺。我們知道,內(nèi)部方法不返回混萝,外部方法是不會(huì)執(zhí)行下一行命令的遗遵。不等到sync函數(shù)返回,viewDidLoad打死也不會(huì)執(zhí)行print End的語(yǔ)句逸嘀,因此车要,viewDidLoad()一直沒(méi)有執(zhí)行完畢。block在等待著viewDidLoad()執(zhí)行完畢崭倘,它才能上翼岁,sync函數(shù)在等待著block執(zhí)行完畢,它才能返回司光,viewDidLoad()在等待著sync函數(shù)返回琅坡,它才能執(zhí)行完畢。這樣的三方循環(huán)等待關(guān)系飘庄,就造成了死鎖脑蠕。


也許文字描述比較抽象购撼,我們?cè)賮?lái)配一幅圖:

![](http://upload-images.jianshu.io/upload_images/2595997-8ee6429e8fef13b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以這么理解:每一個(gè)隊(duì)列跪削,有自己的執(zhí)行室谴仙,串行隊(duì)列的執(zhí)行室,只能容納一個(gè)任務(wù)碾盐,并發(fā)隊(duì)列的執(zhí)行室晃跺,可以同時(shí)容納若干個(gè)任務(wù)。隊(duì)頭的任務(wù)毫玖,只要執(zhí)行室有空位掀虎,就會(huì)被放入執(zhí)行室執(zhí)行。viewDidLoad任務(wù)在執(zhí)行中付枫,我們的主隊(duì)列又是串行隊(duì)列烹玉,執(zhí)行室只能容納一個(gè)任務(wù),那么隊(duì)頭的block就需要等待viewDidLoad執(zhí)行完畢才能進(jìn)入執(zhí)行室阐滩,那么就造成了二打,viewDidLoad永遠(yuǎn)不會(huì)執(zhí)行完畢,block永遠(yuǎn)不能執(zhí)行掂榔。

![](http://upload-images.jianshu.io/upload_images/2595997-9ab1702b34fc39b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

sync函數(shù)永遠(yuǎn)不能返回继效,最終,就是GCD死鎖装获。

- 那么我們可以總結(jié)出GCD被阻塞(blocking)的原因有以下兩點(diǎn):
    1. GCD函數(shù)未返回瑞信,會(huì)阻塞正在執(zhí)行的任務(wù)

    2. 隊(duì)列的執(zhí)行室容量太小,在執(zhí)行室有空位之前穴豫,會(huì)阻塞同一個(gè)隊(duì)列中在等待的任務(wù)

- 注意:阻塞(blocking)和死鎖(deadlock)是不同的意思凡简,阻塞表示需要等待A事件完成后才能完成B事件,稱(chēng)作A會(huì)阻塞B精肃,通俗來(lái)講就是強(qiáng)制等待的意思潘鲫。而死鎖表示由于某些互相阻塞,也就是互相的強(qiáng)制等待肋杖,形成了閉環(huán)溉仑,導(dǎo)致大家永遠(yuǎn)互相阻塞下去了,Always and Forever状植,也就是死鎖浊竟。

#### 解決GCD死鎖
我們已經(jīng)有結(jié)論,造成GCD死鎖津畸,是由于同時(shí)具備以下兩點(diǎn)因素:
1. GCD函數(shù)未返回振定,會(huì)阻塞正在執(zhí)行的任務(wù)

2. 隊(duì)列的執(zhí)行室容量太小,在執(zhí)行室有空位之前肉拓,會(huì)阻塞同一個(gè)隊(duì)列中在等待的任務(wù)

死鎖是由于阻塞閉環(huán)造成的后频,那么我們只用消除其中一個(gè)因素,就能打破這個(gè)閉環(huán),避免死鎖卑惜。

#######方法1: 解決GCD函數(shù)未返回造成的阻塞

- 先提出下面兩個(gè)知識(shí)點(diǎn):
  - dispatch_sync是同步函數(shù)膏执,不具備開(kāi)啟新線程的能力,交給它的block露久,只會(huì)在當(dāng)前線程執(zhí)行更米,不論你傳入的是串行隊(duì)列還是并發(fā)隊(duì)列,并且毫痕,它一定會(huì)等待block被執(zhí)行完畢才返回征峦。
  - dispatch_async是異步函數(shù),具備開(kāi)啟新線程的能力消请,但是不一定會(huì)開(kāi)啟新縣城栏笆,交給它的block,可能在任何線程執(zhí)行臊泰,開(kāi)發(fā)者無(wú)法控制竖伯,是GCD底層在控制。它會(huì)立即返回因宇,不會(huì)等待block被執(zhí)行七婴。



- 注意:以上兩個(gè)知識(shí)點(diǎn),有例外察滑,那就是當(dāng)你傳入的是主隊(duì)列打厘,那兩個(gè)函數(shù)都一定會(huì)安排block在主線程執(zhí)行。記住贺辰,主隊(duì)列是最特殊的隊(duì)列

只要看懂了以上兩個(gè)知識(shí)點(diǎn)户盯,大家就知道,sync函數(shù)未返回會(huì)造成阻塞饲化,只要換成aysnc函數(shù)莽鸭,就會(huì)立即返回,而不會(huì)等待block執(zhí)行吃靠,那么GCD函數(shù)未返回這個(gè)阻塞因素就會(huì)被解決掉硫眨。不用大家也不要盲目的換函數(shù),畢竟兩個(gè)函數(shù)是有不同之處的巢块,要考慮實(shí)際期望礁阁。


#######方法2: 解決隊(duì)列(Queue)阻塞
解決隊(duì)列阻塞,有兩種方法:

1. 為隊(duì)列的執(zhí)行室擴(kuò)容族奢,讓它可以并發(fā)執(zhí)行多個(gè)任務(wù)姥闭,那么就不會(huì)因?yàn)锳任務(wù),造成B任務(wù)被阻塞了越走。
2. 把A和B任務(wù)放在兩個(gè)不同的隊(duì)列中棚品,A就再也沒(méi)有機(jī)會(huì)阻塞B了。因?yàn)槊總€(gè)隊(duì)列都有自己的執(zhí)行室。

首先來(lái)說(shuō)第一個(gè)思路铜跑,如何為隊(duì)列的執(zhí)行室擴(kuò)容呢门怪?我們當(dāng)然沒(méi)有辦法為執(zhí)行室擴(kuò)容,但是我們可以選擇用容量大的隊(duì)列疼进。使用并發(fā)隊(duì)列替代串行隊(duì)列。因?yàn)椴l(fā)隊(duì)列的執(zhí)行室可以同時(shí)容納若干任務(wù)


再來(lái)說(shuō)第二個(gè)思路秧廉,我們來(lái)看代碼:

override func viewDidLoad() {
super.viewDidLoad()
print("Start (NSThread.currentThread())")
let serialQueue = dispatch_queue_create("這是一個(gè)串行隊(duì)列", DISPATCH_QUEUE_SERIAL)
dispatch_sync(serialQueue, {
for i in 0...100{
print("(i) (NSThread.currentThread())")
}
})
print("End (NSThread.currentThread())")
}


![](http://upload-images.jianshu.io/upload_images/2595997-85381d8d2ab036b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我們自己新建了一個(gè)串行隊(duì)列伞广,將block放入自己的串行隊(duì)列,不再和viewDidLoad()處于一個(gè)隊(duì)列疼电,解決了隊(duì)列阻塞嚼锄,因此避免了死鎖問(wèn)題。
網(wǎng)上有一些帖子說(shuō)“在主線程使用sync函數(shù)就會(huì)造成死鎖”或者“在主線程使用sync函數(shù)蔽豺,同時(shí)傳入串行隊(duì)列就會(huì)死鎖”区丑,都是非常錯(cuò)誤的觀念,希望大家能夠真正理解GCD死鎖的原理修陡,而不是死記硬背沧侥。


# 其他
-----

 GCD還有一些其他用法,這里也都提一下魄鸦。

####  線程間通信示例


從子線程回到主線程


dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行耗時(shí)的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程宴杀,執(zhí)行UI刷新操作
});
});


#### 快速迭代
快速遍歷,當(dāng)我們需要遍歷一些耗時(shí)操作拾因,需要它們同時(shí)進(jìn)行旺罢,可以使用dispatch_apply,比如下面的例子是把一個(gè)文件夾中的所有文件剪切到另一個(gè)文件夾中绢记,所有文件近乎同時(shí)剪切扁达。
  • (void)apply
    {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSString *from = @"/Users/Ostkaka/Desktop/From";
    NSString *to = @"/Users/Ostkaka/Desktop/To";

    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];

    dispatch_apply(subpaths.count, queue, ^(size_t index) {
    NSString *subpath = subpaths[index];
    NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
    NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
    // 剪切
    [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];

      NSLog(@"%@---%@", [NSThread currentThread], subpath);
    

    });
    }


#### 柵欄
如果不加dispatch_barrier_async()這行代碼,會(huì)開(kāi)辟四條線程無(wú)序執(zhí)行蠢熄,添加之后會(huì)先執(zhí)行它之前的跪解,結(jié)束后執(zhí)行它之后的。

不過(guò)注意一點(diǎn)签孔,這里一定要?jiǎng)?chuàng)建新的并發(fā)隊(duì)列惠遏,用global是不可以的。

  • (void)barrier
    {
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
    });
    }



#### 延時(shí)執(zhí)行

- iOS常見(jiàn)的延時(shí)執(zhí)行有3種方式

  - 調(diào)用NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調(diào)用self的run方法

- 使用GCD函數(shù)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執(zhí)行這里的代碼...
});

- NSTimer

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];




#### 隊(duì)列組

- 有這么1種需求

- 首先:分別異步執(zhí)行2個(gè)耗時(shí)的操作

- 其次:等2個(gè)異步操作都執(zhí)行完畢后骏啰,再回到主線程執(zhí)行操作

- 如果想要快速高效地實(shí)現(xiàn)上述需求节吮,可以考慮用隊(duì)列組

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后,回到主線程...
});


#### 一次性代碼

使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行1次

static dispatch_once_t onceToken; //記錄是否被執(zhí)行
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});


#### 單例模式

- 作用
    - 可以保證在程序運(yùn)行過(guò)程判耕,一個(gè)類(lèi)只有一個(gè)實(shí)例透绩,而且該實(shí)例易于供外界訪問(wèn),從而方便地控制了實(shí)例個(gè)數(shù),并節(jié)約系統(tǒng)資源

- 使用場(chǎng)合

    - 在整個(gè)應(yīng)用程序中帚豪,共享一份資源(這份資源只需要?jiǎng)?chuàng)建初始化1次)

- 單例模式在ARC\MRC環(huán)境下的寫(xiě)法有所不同碳竟,需要編寫(xiě)2套不同的代碼

- 可以用宏判斷是否為ARC環(huán)境

if __has_feature(objc_arc)

// ARC

else

// MRC

endif


#### 單例模式(ARC)

// 在.m中保留一個(gè)全局的static的實(shí)例
static id _instance;

// 重寫(xiě)allocWithZone:方法,在這里創(chuàng)建唯一的實(shí)例(注意線程安全)

  • (id)allocWithZone:(struct _NSZone *)zone
    {
    @synchronized(self) {
    if (!_instance) {
    _instance = [super allocWithZone:zone];
    }
    }
    return _instance;
    }

提供1個(gè)類(lèi)方法讓外界訪問(wèn)唯一的實(shí)例

  • (instancetype)sharedSoundTool
    {
    @synchronized(self) {
    if (!_instance) {
    _instance = [[self alloc] init];
    }
    }
    return _instance;
    }

實(shí)現(xiàn)copyWithZone:方法

  • (id)copyWithZone:(struct _NSZone *)zone
    {
    return _instance;
    }

#### 單例模式 – MRC

- MRC里狸臣,單例模式的實(shí)現(xiàn)(比ARC多了幾個(gè)步驟)

- 實(shí)現(xiàn)內(nèi)存管理方法

  • (id)retain { return self; }
  • (NSUInteger)retainCount { return 1; }
  • (oneway void)release {}
  • (id)autorelease { return self; }

# 心靈雞湯
------

禪房里莹桅,這個(gè)整日被嫉妒、浮躁烛亦、憂(yōu)慮所困擾的年輕人面對(duì)慈祥诈泼、超然的禪師,一股腦兒倒出了自己的不幸煤禽。禪師笑笑铐达,伸出右手,握成拳頭檬果,“你試試看瓮孙。”年輕人照做选脊『伎伲“再握得緊一些】疑叮”“再緊一些······”于是年輕人把拳頭握得越來(lái)越緊祈争。“感覺(jué)如何角寸?”禪師慈祥的問(wèn)道菩混。年輕人茫然的搖了搖頭。禪師說(shuō):好扁藕,現(xiàn)在可以上下動(dòng)了沮峡。


海的平凡是因?yàn)樗从谝稽c(diǎn)一滴,海的偉大是因?yàn)樗苋菁{千江萬(wàn)河,還有海鮮. 

![](http://upload-images.jianshu.io/upload_images/2595997-bc7155a7fa32a65b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市亿柑,隨后出現(xiàn)的幾起案子邢疙,更是在濱河造成了極大的恐慌,老刑警劉巖望薄,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疟游,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痕支,警方通過(guò)查閱死者的電腦和手機(jī)颁虐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卧须,“玉大人另绩,你說(shuō)我怎么就攤上這事儒陨。” “怎么了笋籽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蹦漠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我车海,道長(zhǎng)笛园,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任侍芝,我火速辦了婚禮研铆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竭贩。我一直安慰自己蚜印,他們只是感情好莺禁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布留量。 她就那樣靜靜地躺著,像睡著了一般哟冬。 火紅的嫁衣襯著肌膚如雪楼熄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天浩峡,我揣著相機(jī)與錄音可岂,去河邊找鬼。 笑死翰灾,一個(gè)胖子當(dāng)著我的面吹牛缕粹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纸淮,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼平斩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了咽块?” 一聲冷哼從身側(cè)響起绘面,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侈沪,沒(méi)想到半個(gè)月后揭璃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亭罪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瘦馍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片应役。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扣墩,死狀恐怖哲银,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呻惕,我是刑警寧澤荆责,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站亚脆,受9級(jí)特大地震影響做院,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜濒持,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一键耕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柑营,春花似錦屈雄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至奶赔,卻和暖如春惋嚎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背站刑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工另伍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绞旅。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓摆尝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親因悲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堕汞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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