GCD

我們知道在iOS開發中,一共有四種多線程技術:pthread,NSThread,GCD,NSOperation:

前兩者是面向線程開發的多線程技術,需要開發者自己去維護線程的生命周期,比較繁瑣。

后兩者是面向隊列開發的多線程技術,開發者僅僅定義想執行的任務追加到適當的Dispatch Queue(隊列)中并設置一些優先級,依賴等操作就可以了,其他的事情可以交給系統來做。

GCD它是基于C語言的API,開發者只需要將任務放在block內,并指定好追加的隊列,就可以完成多線程開發。

但是多線程開發時容易發生的一些問題:

·多個線程更新相同的資源:數據競爭。

·多個線程相互持續等待:死鎖。

·使用太多的線程導致消耗內存。

雖然解決這些問題的代價是會使程序的復雜度上升,但是多線程技術仍然是必須使用的:因為使用多線程編程可以保證應用程序的響應性能。如果耗時操作阻塞了主線程的RunLoop,會導致用戶界面無法響應用戶的操作,所以必須開啟子線程將耗時操作放在子線程中處理。那么我們應該怎么進行多線程開發呢?在講解之前先看一下本文結構(GCD部分):

隊列

Dispatch Queue是執行處理的等待隊列,按照任務(block)追加到隊列里的順序,先進先出執行處理。

而等待隊列有兩種

Serial Dispatch Queue:串行隊列,等待當前執行任務處理結束的隊列。

Concurrent Dispatch Queue:并發隊列,不等待當前執行任務處理結束的隊列。

串行隊列

將任務追加到串行隊列:

- (void)serialQueue{dispatch_queue_tqueue = dispatch_queue_create("serial queue",NULL);for(NSIntegerindex =0; index <6; index ++) {dispatch_async(queue, ^{NSLog(@"task index %ld in serial queue",index);? ? ? ? });? ? }}

輸出:

gcd_demo[33484:2481120] task index0inserial queuegcd_demo[33484:2481120] task index1inserial queuegcd_demo[33484:2481120] task index2inserial queuegcd_demo[33484:2481120] task index3inserial queuegcd_demo[33484:2481120] task index4inserial queuegcd_demo[33484:2481120] task index5inserial queue

通過dispatch_queue_create函數可以創建隊列,第一個函數為隊列的名稱,第二個參數是NULL和DISPATCH_QUEUE_SERIAL時,返回的隊列就是串行隊列。

為了避免重復代碼,我在這里使用了for循環,將任務追加到了queue中。

注意,這里的任務是按照順序執行的。說明任務是以阻塞的形式執行的:必須等待上一個任務執行完成才能執行現在的任務。也就是說:一個Serial Dispatch Queue中同時只能執行一個追加處理(任務block),而且系統對于一個Serial Dispatch Queue只生成并使用一個線程。

但是,如果我們將6個任務分別追加到6個Serial Dispatch Queue中,那么系統就會同時處理這6個任務(因為會另開啟6個子線程):

- (void)multiSerialQueue{for(NSIntegerindex =0; index <10; index ++) {//新建一個serial queuedispatch_queue_tqueue = dispatch_queue_create("different serial queue",NULL);dispatch_async(queue, ^{NSLog(@"serial queue index : %ld",index);? ? ? ? });? ? }}

輸出結果:

gcd_demo[33576:2485282] serial queue index :1gcd_demo[33576:2485264] serial queue index :0gcd_demo[33576:2485267] serial queue index :2gcd_demo[33576:2485265] serial queue index :3gcd_demo[33576:2485291] serial queue index :4gcd_demo[33576:2485265] serial queue index :5

從輸出結果可以看出來,這里的6個任務并不是按順序執行的。

需要注意的是:一旦開發者新建了一個串行隊列,系統一定會開啟一個子線程,所以在使用串行隊列的時候,一定只創建真正需要創建的串行隊列,避免資源浪費。

并發隊列

將任務追加到并發隊列:

- (void)concurrentQueue{dispatch_queue_tqueue = dispatch_queue_create("concurrent queue", DISPATCH_QUEUE_CONCURRENT);for(NSIntegerindex =0; index <6; index ++) {dispatch_async(queue, ^{NSLog(@"task index %ld in concurrent queue",index);? ? ? ? });? ? }}

輸出結果:

gcd_demo[33550:2484160] task index1inconcurrent queuegcd_demo[33550:2484159] task index0inconcurrent queuegcd_demo[33550:2484162] task index2inconcurrent queuegcd_demo[33550:2484182] task index3inconcurrent queuegcd_demo[33550:2484183] task index4inconcurrent queuegcd_demo[33550:2484160] task index5inconcurrent queue

可以看到,dispatch_queue_create函數的第二個參數是DISPATCH_QUEUE_CONCURRENT。

注意,這里追加到并發隊列的6個任務并不是按照順序執行的,符合上面并發隊列的定義。

擴展知識:iOS和OSX基于Dispatch Queue中的處理數,CPU核數,以及CPU負荷等當前系統的狀態來決定Concurrent Dispatch Queue中并發處理的任務數。

隊列的命名

現在我們知道dispatch_queue_create方法第一個參數指定了這個新建隊列的名稱,推薦使用逆序quan cheng全程域名(FQDN,fully qualified domain name)。這個名稱可以在Xcode和CrashLog中顯示出來,對bug的追蹤很有幫助。

在繼續講解之前做個小總結,現在我們知道了:

·如何創建串行隊列和并發隊列。

·將任務追加到這兩種隊列里以后的執行效果。

·將任務追加到多個串行隊列會使這幾個任務在不同的線程執行。

實際上,系統給我們提供了兩種特殊的隊列,分別對應串行隊列和并發隊列:

系統提供的隊列

Main Dispatch Queue

主隊列:放在這個隊列里的任務會追加到主線程的RunLoop中執行。需要刷新UI的時候我們可以直接獲取這個隊列,將任務追加到這個隊列中。

Globle Dispatch Queue

全局并發隊列:開發者可以不需要特意通過dispatch_queue_create方法創建一個Concurrent Dispatch Queue,可以將任務直接放在這個全局并發隊列里面。

有一個常見的例子可以充分體現二者的使用方法:

//獲取全局并發隊列進行耗時操作dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{//加載圖片NSData*dataFromURL = [NSDatadataWithContentsOfURL:imageURL];UIImage*imageFromData = [UIImageimageWithData:dataFromURL];dispatch_async(dispatch_get_main_queue(), ^{//獲取主隊列,在圖片加載完成后更新UIImageViewUIImageView*imageView = [[UIImageViewalloc] initWithImage:imageFromData];? ? ? ? ? ? ? ? });? ? ? ? });

GCD的各種函數

dispatch_set_target_queue

這個函數有兩個作用:

·改變隊列的優先級。

·防止多個串行隊列的并發執行。

改變隊列的優先級

dispatch_queue_create方法生成的串行隊列合并發隊列的優先級都是與默認優先級的Globle Dispatch Queue一致。

如果想要變更某個隊列的優先級,需要使用dispatch_set_target_queue函數。

舉個??:創建一個在后臺執行動作處理的Serial Dispatch Queue

//需求:生成一個后臺的串行隊列- (void)changePriority{dispatch_queue_tqueue = dispatch_queue_create("queue",NULL);dispatch_queue_tbgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);//第一個參數:需要改變優先級的隊列;//第二個參數:目標隊列dispatch_set_target_queue(queue, bgQueue);}

防止多個串行隊列的并發執行

有時,我們將不能并發執行的處理追加到多個Serial Dispatch Queue中時,可以使用dispatch_set_target_queue函數將目標函數定為某個Serial Dispatch Queue,就可以防止這些處理的并發執行。

代碼:

NSMutableArray*array = [NSMutableArrayarray];for(NSIntegerindex =0; index <5; index ++) {//5個串行隊列dispatch_queue_tserial_queue = dispatch_queue_create("serial_queue",NULL);? ? ? ? [array addObject:serial_queue];}[array enumerateObjectsUsingBlock:^(dispatch_queue_tqueue,NSUIntegeridx,BOOL* _Nonnull stop) {dispatch_async(queue, ^{NSLog(@"任務%ld",idx);? ? });}];

輸出:

gcd_demo[40329:2999714] 任務1gcd_demo[40329:2999726] 任務0gcd_demo[40329:2999717] 任務2gcd_demo[40329:2999715] 任務3gcd_demo[40329:2999730] 任務4

我們可以看到,如果僅僅是將任務追加到5個串行隊列中,那么這些任務就會并發執行。

那接下來看看使用dispatch_set_target_queue方法以后:

//多個串行隊列,設置了target queueNSMutableArray*array = [NSMutableArrayarray];dispatch_queue_tserial_queue_target = dispatch_queue_create("queue_target",NULL);for(NSIntegerindex =0; index <5; index ++) {//分別給每個隊列設置相同的target queuedispatch_queue_tserial_queue = dispatch_queue_create("serial_queue",NULL);? ? dispatch_set_target_queue(serial_queue, serial_queue_target);? ? [array addObject:serial_queue];}[array enumerateObjectsUsingBlock:^(dispatch_queue_tqueue,NSUIntegeridx,BOOL* _Nonnull stop) {dispatch_async(queue, ^{NSLog(@"任務%ld",idx);? ? });}];

輸出:

gcd_demo[40408:3004382] 任務0gcd_demo[40408:3004382] 任務1gcd_demo[40408:3004382] 任務2gcd_demo[40408:3004382] 任務3gcd_demo[40408:3004382] 任務4

很顯然,這些任務就按順序執行了。

dispatch_after

dispatch_after解決的問題:某個線程里,在指定的時間后處理某個任務:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"三秒之后追加到隊列");});

注意:不是在3秒之后處理任務,準確來說是3秒之后追加到隊列。所以說,如果這個線程的runloop執行1/60秒一次,那么這個block最快會在3秒后執行,最慢會在(3+1/60)秒后執行。而且,如果這個隊列本身還有延遲,那么這個block的延遲執行時間會更多。

dispatch_group

·如果遇到這樣到需求:全部處理完多個預處理任務(block_1 ~ 4)后執行某個任務(block_finish),我們有兩個方法:

·如果預處理任務需要一個接一個的執行:將所有需要先處理完的任務追加到Serial Dispatch Queue中,并在最后追加最后處理的任務(block_finish)。

·如果預處理任務需要并發執行:需要使用dispatch_group函數,將這些預處理的block追加到global dispatch queue中。

分別詳細講解一下兩種需求的實現方式:

預處理任務需要一個接一個的執行:

這個需求的實現方式相對簡單一點,只要將所有的任務(block_1 ~ 4 + block_finish)放在一個串行隊列中即可,因為都是按照順序執行的,只要不做多余的事情,這些任務就會乖乖地按順序執行。

預處理任務需要一個接一個的執行:

dispatch_group_t group = dispatch_group_create();dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);for(NSIntegerindex =0; index <5; index ++) {? ? ? ? dispatch_group_async(group, queue, ^{NSLog(@"任務%ld",index);? ? ? ? });}dispatch_group_notify(group, queue, ^{NSLog(@"最后的任務");});

輸出:

gcd_demo[40905:3057237] 任務0gcd_demo[40905:3057235] 任務1gcd_demo[40905:3057234] 任務2gcd_demo[40905:3057253] 任務3gcd_demo[40905:3057237] 任務4gcd_demo[40905:3057237] 最后的任務

因為這些預處理任務都是追加到global dispatch queue中的,所以這些任務的執行任務的順序是不定的。但是最后的任務一定是最后輸出的。

dispatch_group_notify函數監聽傳入的group中任務的完成,等這些任務全部執行以后,再將第三個參數(block)追加到第二個參數的queue(相同的queue)中。

dispatch_group_wait

dispatch_group_wait 也是配合dispatch_group 使用的,利用這個函數,我們可以設定group內部所有任務執行完成的超時時間。

一共有兩種情況:超時的情況和沒有超時的情況:

超時的情況:

- (void)dispatch_wait_1{? ? dispatch_group_t group = dispatch_group_create();dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);for(NSIntegerindex =0; index <5; index ++) {? ? ? ? dispatch_group_async(group, queue, ^{for(NSIntegeri =0; i<1000000000; i ++) {? ? ? ? ? ? }NSLog(@"任務%ld",index);? ? ? ? });? ? }? ? dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull *NSEC_PER_SEC);longresult = dispatch_group_wait(group, time);if(result ==0) {NSLog(@"group內部的任務全部結束");? ? }else{NSLog(@"雖然過了超時時間,group還有任務沒有完成");? ? }}

輸出:

gcd_demo[41277:3087481] 雖然過了超時時間,group還有任務沒有完成,結果是判定為超時gcd_demo[41277:3087563] 任務0gcd_demo[41277:3087564] 任務2gcd_demo[41277:3087579] 任務3gcd_demo[41277:3087566] 任務1gcd_demo[41277:3087563] 任務4

沒有超時的情況:

- (void)dispatch_wait_2{? ? dispatch_group_t group = dispatch_group_create();dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);for(NSIntegerindex =0; index <5; index ++) {? ? ? ? dispatch_group_async(group, queue, ^{for(NSIntegeri =0; i<100000000; i ++) {? ? ? ? ? ? }NSLog(@"任務%ld",index);? ? ? ? });? ? }? ? dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull *NSEC_PER_SEC);longresult = dispatch_group_wait(group, time);if(result ==0) {NSLog(@"group內部的任務全部結束");? ? }else{NSLog(@"雖然過了超時時間,group還有任務沒有完成");? ? }}

輸出:

gcd_demo[41357:3092079] 任務2gcd_demo[41357:3092076] 任務3gcd_demo[41357:3092092] 任務1gcd_demo[41357:3092077] 任務0gcd_demo[41357:3092079] 任務4gcd_demo[41357:3091956] group內部的任務全部結束,在超時的時間以內完成,結果判定為沒有超時

注意:

一旦調用dispatch_group_wait以后,當經過了函數中指定的超時時間后 或者 指定的group內的任務全部執行后會返回這個函數的結果:

經過了函數中指定的超時時間后,group內部的任務沒有全部完成,判定為超時,否則,沒有超時

指定的group內的任務全部執行后,經過的時間長于超時時間,判定為超時,否則,沒有超時。

也就是說:

如果指定的超時時間為DISPATCH_TIME_NOW,那么則沒有等待,立即判斷group內的任務是否完成。

可以看出,指定的超時時間為DISPATCH_TIME_NOW的時候相當于dispatch_group_notify函數的使用:判斷group內的任務是否都完成。

然而dispatch_group_notify函數是作者推薦的,因為通過這個函數可以直接設置最后任務所被追加的隊列,使用起來相對比較方便。

dispatch_barrier_async

關于解決數據競爭的方法:讀取處理是可以并發的,但是寫入處理卻是不允許并發執行的。

所以合理的方案是這樣的:

·讀取處理追加到concurrent dispatch queue中

·寫入處理在任何一個讀取處理沒有執行的狀態下,追加到serial dispatch queue中(也就是說,在寫入處理結束之前,讀取處理不可執行)。

我們看看如何使用dispatch_barrier_async來解決這個問題。

為了幫助大家理解,我構思了一個例子:

·3名董事和總裁開會,在每個人都查看完合同之后,由總裁簽字。

·總裁簽字之后,所有人再審核一次合同。

這個需求有三個關鍵點:

·關鍵點1:所有與會人員查看和審核合同,是同時進行的,無序的行為。

·關鍵點2:只有與會人員都查看了合同之后,總裁才能簽字。

·關鍵點3: 只有總裁簽字之后,才能進行審核。

用代碼看一下:

- (void)dispatch_barrier{dispatch_queue_tmeetingQueue = dispatch_queue_create("com.meeting.queue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(meetingQueue, ^{NSLog(@"總裁查看合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事1查看合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事2查看合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事3查看合同");? ? });? ? dispatch_barrier_async(meetingQueue, ^{NSLog(@"總裁簽字");? ? });dispatch_async(meetingQueue, ^{NSLog(@"總裁審核合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事1審核合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事2審核合同");? ? });dispatch_async(meetingQueue, ^{NSLog(@"董事3審核合同");? ? });}

輸出結果:

gcd_demo[41791:3140315] 總裁查看合同gcd_demo[41791:3140296] 董事1查看合同gcd_demo[41791:3140297] 董事3查看合同gcd_demo[41791:3140299] 董事2查看合同gcd_demo[41791:3140299] 總裁簽字gcd_demo[41791:3140299] 總裁審核合同gcd_demo[41791:3140297] 董事1審核合同gcd_demo[41791:3140296] 董事2審核合同gcd_demo[41791:3140320] 董事3審核合同

在這里,我們可以將meetingQueue看成是會議的時間線。總裁簽字這個行為相當于寫操作,其他都相當于讀操作。使用dispatch_barrier_async以后,之前的所有并發任務都會被dispatch_barrier_async里的任務攔截掉,就像函數名稱里的“柵欄”一樣。

因此,使用Concurrent Dispatch Queue 和 dispatch_barrier_async 函數可以實現高效率的數據庫訪問和文件訪問。

dispatch_sync

到目前為止的所有例子都使用的是異步函數,有異步就一定會有同步,那么現在就來區分一下同步和異步函數的區別:

·dispatch_async:異步函數,這個函數會立即返回,不做任何等待,它所指定的block“非同步地”追加到指定的隊列中。

·dispatch_sync:同步函數,這個函數不會立即返回,它會一直等待追加到特定隊列中的制定block完成工作后才返回,所以它的目的(也是效果)是阻塞當前線程。

舉個例子:

- (void)dispatch_sync_1{//同步處理NSLog(@"%@",[NSThreadcurrentThread]);NSLog(@"同步處理開始");? ? __blockNSIntegernum =0;dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_sync(queue, ^{//模仿耗時操作for(NSIntegeri =0; i<1000000000; i ++) {? ? ? ? ? ? num++;? ? ? ? }NSLog(@"%@",[NSThreadcurrentThread]);NSLog(@"同步處理完畢");? ? });NSLog(@"%ld",num);NSLog(@"%@",[NSThreadcurrentThread]);}

輸出結果:

gcd_demo[5604:188687] {number =1, name = main}gcd_demo[5604:188687] 同步處理開始gcd_demo[5604:188687] {number =1, name = main}gcd_demo[5604:188687] 同步處理完畢gcd_demo[5604:188687]1000000000gcd_demo[5604:188687] {number =1, name = main}

在最開始的時候只打印前兩行,循環完畢之后才打印后面的內容。

因為是同步函數,它阻塞了當前線程(主線程),所以只能等到block內部的任務都結束后,才能打印下面的兩行。

但是如果使用異步函數會怎樣呢?

- (void)dispatch_sync_2{//異步處理NSLog(@"%@",[NSThreadcurrentThread]);NSLog(@"異步處理開始");? ? __blockNSIntegernum =0;dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_async(queue, ^{//模仿耗時操作for(NSIntegeri =0; i<1000000000; i ++) {? ? ? ? ? ? num++;? ? ? ? }NSLog(@"%@",[NSThreadcurrentThread]);NSLog(@"異步處理完畢");? ? });NSLog(@"%ld",num);NSLog(@"%@",[NSThreadcurrentThread]);}

輸出:

gcd_demo[5685:194233] {number =1, name = main}gcd_demo[5685:194233] 異步處理開始gcd_demo[5685:194233]0gcd_demo[5685:194233] {number =1, name = main}gcd_demo[5685:194280] {number =3, name = (null)}gcd_demo[5685:194280] 異步處理完畢

我們可以看到,不同于上面的情況,block下面的兩個輸出是先打印的(因為沒有經過for循環的計算,num的值是0)。因為是異步處理,所以沒有等待block中任務的完成就立即返回了。

了解了同步異步的區別之后,我們看一下使用同步函數容易發生的問題:如果給同步函數傳入的隊列是串行隊列的時候就會容易造成死鎖。看一下一個死鎖的例子:

- (void)dispatch_sync_3{NSLog(@"任務1");dispatch_queue_tqueue = dispatch_get_main_queue();dispatch_sync(queue, ^{NSLog(@"任務2");? ? });NSLog(@"任務3");}

上面的代碼只能輸出任務1,并形成死鎖。

因為任務2被追加到了主隊列的最后,所以它需要等待任務3執行完成。

但又因為是同步函數,任務3也在等待任務2執行完成。

二者互相等待,所以形成了死鎖。

dispatch_apply

通過dispatch_apply函數,我們可以按照指定的次數將block追加到指定的隊列中。并等待全部處理執行結束。

- (void)dispatch_apply_1{dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);? ? dispatch_apply(10, queue, ^(size_t index) {NSLog(@"%ld",index);? ? });NSLog(@"完畢");}

gcd_demo[6128:240332]1gcd_demo[6128:240331]0gcd_demo[6128:240334]2gcd_demo[6128:240332]4gcd_demo[6128:240334]6gcd_demo[6128:240331]5gcd_demo[6128:240332]7gcd_demo[6128:240334]8gcd_demo[6128:240331]9gcd_demo[6128:240259]3gcd_demo[6128:240259] 完畢

我們也可以用這個函數來遍歷數組,取得下標進行操作:

- (void)dispatch_apply_2{NSArray*array = @[@1,@10,@43,@13,@33];dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);? ? dispatch_apply([array count], queue, ^(size_t index) {NSLog(@"%@",array[index]);? ? });NSLog(@"完畢");}

輸出:

gcd_demo[6180:244316]10gcd_demo[6180:244313]1gcd_demo[6180:244316]33gcd_demo[6180:244314]43gcd_demo[6180:244261]13gcd_demo[6180:244261] 完畢

我們可以看到dispatch_apply函數與dispatch_sync函數同樣具有阻塞的作用(dispatch_apply函數返回后才打印完畢)。

我們也可以在dispatch_async函數里執行dispatch_apply函數:

- (void)dispatch_apply_3{dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_async(queue, ^{NSArray*array = @[@1,@10,@43,@13,@33];? ? ? ? __blockNSIntegersum =0;? ? ? ? dispatch_apply([array count], queue, ^(size_t index) {NSNumber*number = array[index];NSIntegernum = [number integerValue];? ? ? ? ? ? sum += num;? ? ? ? });dispatch_async(dispatch_get_main_queue(), ^{//回到主線程,拿到總和NSLog(@"完畢");NSLog(@"%ld",sum);? ? ? ? });? ? });}

dispatch_suspend/dispatch_resume

掛起函數調用后對已經執行的處理沒有影響,但是追加到隊列中但是尚未執行的處理會在此之后停止執行。

dispatch_suspend(queue);

dispatch_resume(queue);

dispatch_once

通過dispatch_once處理的代碼只執行一次,而且是線程安全的:

- (void)dispatch_once_1{dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);for(NSIntegerindex =0; index <5; index++) {dispatch_async(queue, ^{? ? ? ? ? ? [selfonceCode];? ? ? ? });? ? }}- (void)onceCode{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{NSLog(@"只執行一次的代碼");? ? });}

輸出:

gcd_demo[7556:361196] 只執行一次的代碼

該函數主要用于單例模式的使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容