我們知道在iOS開發中,一共有四種多執行緒技術:pthread,NSThread,GCD,NSOperation:
- 前兩者是面向執行緒開發的多執行緒技術,需要開發者自己去維護執行緒的生命週期,比較繁瑣。
- 後兩者是面向佇列開發的多執行緒技術,開發者僅僅定義想執行的任務追加到適當的Dispatch Queue(佇列)中並設定一些優先順序,依賴等操作就可以了,其他的事情可以交給系統來做。
本篇是這一系列:iOS – 《Objective-C 高階程式設計》的最後一篇,講解了本書的第三章。在這一章裡,作者主要介紹了GCD技術,它是基於C語言的API,開發者只需要將任務放在block內,並指定好追加的佇列,就可以完成多執行緒開發。
但是多執行緒開發時容易發生的一些問題:
- 多個執行緒更新相同的資源:資料競爭。
- 多個執行緒相互持續等待:死鎖。
- 使用太多的執行緒導致消耗記憶體。
雖然解決這些問題的代價是會使程式的複雜度上升,但是多執行緒技術仍然是必須使用的:因為使用多執行緒程式設計可以保證應用程式的響應效能。如果耗時操作阻塞了主執行緒的RunLoop,會導致使用者介面無法響應使用者的操作,所以必須開啟子執行緒將耗時操作放在子執行緒中處理。那麼我們應該怎麼進行多執行緒開發呢?在講解之前先看一下本文結構(GCD部分):
本文的Demo地址:knightsj/iOS_Demo/gcd_demo
雖然文章裡應給出了詳細的輸出結果,但還是希望讀者可以將demo下載後仔細對照一下程式碼並體會。
佇列
Dispatch Queue是執行處理的等待佇列,按照任務(block)追加到佇列裡的順序,先進先出執行處理。
而等待佇列有兩種
- Serial Dispatch Queue:序列佇列,等待當前執行任務處理結束的佇列。
- Concurrent Dispatch Queue:併發佇列,不等待當前執行任務處理結束的佇列。
序列佇列
將任務追加到序列佇列:
- (void)serialQueue
{
dispatch_queue_t queue = dispatch_queue_create("serial queue", NULL);
for (NSInteger index = 0; index < 6; index ++) {
dispatch_async(queue, ^{
NSLog(@"task index %ld in serial queue",index);
});
}
}
複製程式碼
輸出:
gcd_demo[33484:2481120] task index 0 in serial queue
gcd_demo[33484:2481120] task index 1 in serial queue
gcd_demo[33484:2481120] task index 2 in serial queue
gcd_demo[33484:2481120] task index 3 in serial queue
gcd_demo[33484:2481120] task index 4 in serial queue
gcd_demo[33484:2481120] task index 5 in serial 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 (NSInteger index = 0; index < 10; index ++) {
//新建一個serial queue
dispatch_queue_t queue = dispatch_queue_create("different serial queue", NULL);
dispatch_async(queue, ^{
NSLog(@"serial queue index : %ld",index);
});
}
}
複製程式碼
輸出結果:
gcd_demo[33576:2485282] serial queue index : 1
gcd_demo[33576:2485264] serial queue index : 0
gcd_demo[33576:2485267] serial queue index : 2
gcd_demo[33576:2485265] serial queue index : 3
gcd_demo[33576:2485291] serial queue index : 4
gcd_demo[33576:2485265] serial queue index : 5
複製程式碼
從輸出結果可以看出來,這裡的6個任務並不是按順序執行的。
需要注意的是:一旦開發者新建了一個序列佇列,系統一定會開啟一個子執行緒,所以在使用序列佇列的時候,一定只建立真正需要建立的序列佇列,避免資源浪費。
併發佇列
將任務追加到併發佇列:
- (void)concurrentQueue
{
dispatch_queue_t queue = dispatch_queue_create("concurrent queue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger index = 0; index < 6; index ++) {
dispatch_async(queue, ^{
NSLog(@"task index %ld in concurrent queue",index);
});
}
}
複製程式碼
輸出結果:
gcd_demo[33550:2484160] task index 1 in concurrent queue
gcd_demo[33550:2484159] task index 0 in concurrent queue
gcd_demo[33550:2484162] task index 2 in concurrent queue
gcd_demo[33550:2484182] task index 3 in concurrent queue
gcd_demo[33550:2484183] task index 4 in concurrent queue
gcd_demo[33550:2484160] task index 5 in concurrent 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 = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageFromData = [UIImage imageWithData:dataFromURL];
dispatch_async(dispatch_get_main_queue(), ^{
//獲取主佇列,在圖片載入完成後更新UIImageView
UIImageView *imageView = [[UIImageView alloc] initWithImage:imageFromData];
});
});
複製程式碼
GCD的各種函式
dispatch_set_target_queue
這個函式有兩個作用:
- 改變佇列的優先順序。
- 防止多個序列佇列的併發執行。
改變佇列的優先順序
dispatch_queue_create方法生成的序列佇列合併發佇列的優先順序都是與預設優先順序的Globle Dispatch Queue一致。
如果想要變更某個佇列的優先順序,需要使用dispatch_set_target_queue函式。
舉個?:建立一個在後臺執行動作處理的Serial Dispatch Queue
//需求:生成一個後臺的序列佇列
- (void)changePriority
{
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_queue_t bgQueue = 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 = [NSMutableArray array];
for (NSInteger index = 0; index < 5; index ++) {
//5個序列佇列
dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", NULL);
[array addObject:serial_queue];
}
[array enumerateObjectsUsingBlock:^(dispatch_queue_t queue, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_async(queue, ^{
NSLog(@"任務%ld",idx);
});
}];
複製程式碼
輸出:
gcd_demo[40329:2999714] 任務1
gcd_demo[40329:2999726] 任務0
gcd_demo[40329:2999717] 任務2
gcd_demo[40329:2999715] 任務3
gcd_demo[40329:2999730] 任務4
複製程式碼
我們可以看到,如果僅僅是將任務追加到5個序列佇列中,那麼這些任務就會併發執行。
那接下來看看使用dispatch_set_target_queue方法以後:
//多個序列佇列,設定了target queue
NSMutableArray *array = [NSMutableArray array];
dispatch_queue_t serial_queue_target = dispatch_queue_create("queue_target", NULL);
for (NSInteger index = 0; index < 5; index ++) {
//分別給每個佇列設定相同的target queue
dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", NULL);
dispatch_set_target_queue(serial_queue, serial_queue_target);
[array addObject:serial_queue];
}
[array enumerateObjectsUsingBlock:^(dispatch_queue_t queue, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_async(queue, ^{
NSLog(@"任務%ld",idx);
});
}];
複製程式碼
輸出:
gcd_demo[40408:3004382] 任務0
gcd_demo[40408:3004382] 任務1
gcd_demo[40408:3004382] 任務2
gcd_demo[40408:3004382] 任務3
gcd_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_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger index = 0; index < 5; index ++) {
dispatch_group_async(group, queue, ^{
NSLog(@"任務%ld",index);
});
}
dispatch_group_notify(group, queue, ^{
NSLog(@"最後的任務");
});
複製程式碼
輸出:
gcd_demo[40905:3057237] 任務0
gcd_demo[40905:3057235] 任務1
gcd_demo[40905:3057234] 任務2
gcd_demo[40905:3057253] 任務3
gcd_demo[40905:3057237] 任務4
gcd_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_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger index = 0; index < 5; index ++) {
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i< 1000000000; i ++) {
}
NSLog(@"任務%ld",index);
});
}
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"group內部的任務全部結束");
}else{
NSLog(@"雖然過了超時時間,group還有任務沒有完成");
}
}
複製程式碼
輸出:
gcd_demo[41277:3087481] 雖然過了超時時間,group還有任務沒有完成,結果是判定為超時
gcd_demo[41277:3087563] 任務0
gcd_demo[41277:3087564] 任務2
gcd_demo[41277:3087579] 任務3
gcd_demo[41277:3087566] 任務1
gcd_demo[41277:3087563] 任務4
複製程式碼
沒有超時的情況:
- (void)dispatch_wait_2
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger index = 0; index < 5; index ++) {
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i< 100000000; i ++) {
}
NSLog(@"任務%ld",index);
});
}
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"group內部的任務全部結束");
}else{
NSLog(@"雖然過了超時時間,group還有任務沒有完成");
}
}
複製程式碼
輸出:
gcd_demo[41357:3092079] 任務2
gcd_demo[41357:3092076] 任務3
gcd_demo[41357:3092092] 任務1
gcd_demo[41357:3092077] 任務0
gcd_demo[41357:3092079] 任務4
gcd_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_t meetingQueue = 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(@"%@",[NSThread currentThread]);
NSLog(@"同步處理開始");
__block NSInteger num = 0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//模仿耗時操作
for (NSInteger i = 0; i< 1000000000; i ++) {
num++;
}
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"同步處理完畢");
});
NSLog(@"%ld",num);
NSLog(@"%@",[NSThread currentThread]);
}
複製程式碼
輸出結果:
gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
gcd_demo[5604:188687] 同步處理開始
gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
gcd_demo[5604:188687] 同步處理完畢
gcd_demo[5604:188687] 1000000000
gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
複製程式碼
在最開始的時候只列印前兩行,迴圈完畢之後才列印後面的內容。
因為是同步函式,它阻塞了當前執行緒(主執行緒),所以只能等到block內部的任務都結束後,才能列印下面的兩行。
但是如果使用非同步函式會怎樣呢?
- (void)dispatch_sync_2
{
//非同步處理
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"非同步處理開始");
__block NSInteger num = 0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//模仿耗時操作
for (NSInteger i = 0; i< 1000000000; i ++) {
num++;
}
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"非同步處理完畢");
});
NSLog(@"%ld",num);
NSLog(@"%@",[NSThread currentThread]);
}
複製程式碼
輸出:
gcd_demo[5685:194233] <NSThread: 0x600000071f00>{number = 1, name = main}
gcd_demo[5685:194233] 非同步處理開始
gcd_demo[5685:194233] 0
gcd_demo[5685:194233] <NSThread: 0x600000071f00>{number = 1, name = main}
gcd_demo[5685:194280] <NSThread: 0x608000260400>{number = 3, name = (null)}
gcd_demo[5685:194280] 非同步處理完畢
複製程式碼
我們可以看到,不同於上面的情況,block下面的兩個輸出是先列印的(因為沒有經過for迴圈的計算,num的值是0)。因為是非同步處理,所以沒有等待block中任務的完成就立即返回了。
瞭解了同步非同步的區別之後,我們看一下使用同步函式容易發生的問題:如果給同步函式傳入的佇列是序列佇列的時候就會容易造成死鎖。看一下一個死鎖的例子:
- (void)dispatch_sync_3
{
NSLog(@"任務1");
dispatch_queue_t queue = 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_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%ld",index);
});
NSLog(@"完畢");
}
複製程式碼
gcd_demo[6128:240332] 1
gcd_demo[6128:240331] 0
gcd_demo[6128:240334] 2
gcd_demo[6128:240332] 4
gcd_demo[6128:240334] 6
gcd_demo[6128:240331] 5
gcd_demo[6128:240332] 7
gcd_demo[6128:240334] 8
gcd_demo[6128:240331] 9
gcd_demo[6128:240259] 3
gcd_demo[6128:240259] 完畢
複製程式碼
我們也可以用這個函式來遍歷陣列,取得下標進行操作:
- (void)dispatch_apply_2
{
NSArray *array = @[@1,@10,@43,@13,@33];
dispatch_queue_t queue = 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] 10
gcd_demo[6180:244313] 1
gcd_demo[6180:244316] 33
gcd_demo[6180:244314] 43
gcd_demo[6180:244261] 13
gcd_demo[6180:244261] 完畢
複製程式碼
我們可以看到dispatch_apply函式與dispatch_sync函式同樣具有阻塞的作用(dispatch_apply函式返回後才列印完畢)。
我們也可以在dispatch_async函式裡執行dispatch_apply函式:
- (void)dispatch_apply_3
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSArray *array = @[@1,@10,@43,@13,@33];
__block NSInteger sum = 0;
dispatch_apply([array count], queue, ^(size_t index) {
NSNumber *number = array[index];
NSInteger num = [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_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger index = 0; index < 5; index++) {
dispatch_async(queue, ^{
[self onceCode];
});
}
}
- (void)onceCode
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"只執行一次的程式碼");
});
}
複製程式碼
輸出:
gcd_demo[7556:361196] 只執行一次的程式碼
複製程式碼
該函式主要用於單例模式的使用。
到這裡終於總結完啦,這本書加深了我對iOS記憶體管理,block以及GCD的理解,希望我寫的這三篇能對您有所幫助~
本文已經同步到個人部落格:傳送門
—————————- 2018年7月17日更新 —————————-
注意注意!!!
筆者在近期開通了個人公眾號,主要分享程式設計,讀書筆記,思考類的文章。
- 程式設計類文章:包括筆者以前釋出的精選技術文章,以及後續釋出的技術文章(以原創為主),並且逐漸脫離 iOS 的內容,將側重點會轉移到提高程式設計能力的方向上。
- 讀書筆記類文章:分享程式設計類,思考類,心理類,職場類書籍的讀書筆記。
- 思考類文章:分享筆者平時在技術上,生活上的思考。
因為公眾號每天釋出的訊息數有限制,所以到目前為止還沒有將所有過去的精選文章都發布在公眾號上,後續會逐步釋出的。
而且因為各大部落格平臺的各種限制,後面還會在公眾號上釋出一些短小精幹,以小見大的乾貨文章哦~
掃下方的公眾號二維碼並點選關注,期待與您的共同成長~