關(guān)于GCD網(wǎng)上很多介紹, 大部分人都很熟悉, 這里只是本人學(xué)習(xí)的一個(gè)總結(jié), 不到之處,還請(qǐng)指正.
一. 準(zhǔn)備
在這之前, 我們需要明白幾個(gè)比較容易混淆的概念:同步, 異步, 并發(fā), 串行;
- 同步和異步?jīng)Q定了要不要開(kāi)啟新的線程
1.同步: 在當(dāng)前線程中執(zhí)行任務(wù), 不具備開(kāi)啟新線程的能力
2.異步: 在新的線程中執(zhí)行任務(wù), 具備開(kāi)啟新線程的能
- 并發(fā)和串行決定了任務(wù)的執(zhí)行方式
1.并發(fā): 多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
2.串行: 一個(gè)任務(wù)執(zhí)行完畢后, 再執(zhí)行下一個(gè)任務(wù)(順序執(zhí)行)
明白了這幾個(gè)概念, 再去看GCD相關(guān)的一些內(nèi)容就會(huì)比較清晰了.
二. CGD中的隊(duì)列
1. 串行隊(duì)列
GCD中獲取串行隊(duì)列有兩種途徑:
第一種是使用dispatch_queue_create函數(shù)創(chuàng)建串行隊(duì)列:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
參數(shù):
- label: 隊(duì)列名稱, 是一個(gè)C的字符串, 可自定義
- attr: 隊(duì)列類型, 如果傳NULL, 就是串行隊(duì)列; 傳DISPATCH_QUEUE_CONCURRENT,可創(chuàng)建并行隊(duì)列
例如:
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue= dispatch_queue_create("串行隊(duì)列名稱", NULL);
// 非ARC需要釋放手動(dòng)創(chuàng)建的隊(duì)列, 現(xiàn)在一般用不到
dispatch_release(queue);
第二種就是直接使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)
主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列, 放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行, 使用dispatch_get_main_queue()獲得主隊(duì)列;
例如:
// 獲取主隊(duì)列(串行隊(duì)列)
dispatch_queue_t queue = dispatch_get_main_queue();
2. 并行隊(duì)列
并行隊(duì)列的獲取也有兩種方式, 第一種上面也有提到, 就是使用 dispatch_queue_create 來(lái)創(chuàng)建, 主要第二個(gè)參數(shù):
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
但是, 我們一般不使用這個(gè)方式來(lái)獲取并行隊(duì)列, 而是使用系統(tǒng)預(yù)定義的全局并發(fā)隊(duì)列;
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊(duì)列:
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);;
參數(shù):
- identifier: 隊(duì)列的優(yōu)先級(jí), 系統(tǒng)預(yù)定義了四種, 以供開(kāi)發(fā)者使用
//全局并發(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)
- flags: 這個(gè)參數(shù)是預(yù)留給以后使用的, 暫時(shí)用不上, 傳 0 即可;
例如:
// 獲得默認(rèn)優(yōu)先級(jí)的全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 同步, 異步
同步異步的開(kāi)啟主要是使用下面的函數(shù):
void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
同步執(zhí)行函數(shù), 參數(shù):
- queue: 在哪個(gè)隊(duì)列執(zhí)行
- block: 執(zhí)行的任務(wù)
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
異步執(zhí)行函數(shù), 參數(shù):
- queue: 在哪個(gè)隊(duì)列執(zhí)行
- block: 執(zhí)行的任務(wù)
三. 同步, 異步, 串行, 并發(fā)組合測(cè)試
下面就來(lái)通過(guò)實(shí)例看一下, 同步, 異步, 串行, 并發(fā)直接的一些聯(lián)系和區(qū)別;
測(cè)試一: 用同步函數(shù)往串行隊(duì)列中添加任務(wù)
不會(huì)開(kāi)啟新的線程:
NSLog(@"用同步函數(shù)往串行隊(duì)列中添加任務(wù)");
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue= dispatch_queue_create("LZQueueName", NULL);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
sleep(3);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
sleep(2);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
輸出:
2017-01-06 10:11:32.135 LZGCDTest[2938:57629] 用同步函數(shù)往串行隊(duì)列中添加任務(wù)
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 主線程----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 下載圖片1----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:35.209 LZGCDTest[2938:57629] 下載圖片2----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:37.280 LZGCDTest[2938:57629] 下載圖片3----<NSThread: 0x608000066240>{number = 1, name = main}
可以看到, 是按照任務(wù)的添加順序執(zhí)行的, 而且是在當(dāng)前線程(主線程)中執(zhí)行的, 沒(méi)有開(kāi)啟新線程(同步函數(shù)不具備開(kāi)啟新線程的能力);
測(cè)試二: 用同步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
不會(huì)開(kāi)啟新的線程((同步函數(shù)不具備開(kāi)啟新線程的能力)),并發(fā)隊(duì)列失去了并發(fā)的功能:
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//獲取并行隊(duì)列
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
sleep(3);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
sleep(2);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
輸出:
2017-01-06 10:08:37.874 LZGCDTest[2859:55553] 主線程----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:42.892 LZGCDTest[2859:55553] 下載圖片1----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:45.966 LZGCDTest[2859:55553] 下載圖片2----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:48.035 LZGCDTest[2859:55553] 下載圖片3----<NSThread: 0x60800007adc0>{number = 1, name = main}
可以看出, 雖然使用的是并發(fā)隊(duì)列, 但是使用的是同步函數(shù), 由于同步函數(shù)沒(méi)有開(kāi)啟新線程的能力, 所以并發(fā)隊(duì)列就失去了并發(fā)性, 按照任務(wù)的添加順序, 順序執(zhí)行;
測(cè)試三. 用異步函數(shù)往串行隊(duì)列中添加任務(wù)
會(huì)開(kāi)啟線程掂碱,但是只開(kāi)啟一個(gè)線程:
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//創(chuàng)建串行隊(duì)列,iOS4.3之后:第二個(gè)參數(shù)可寫 DISPATCH_QUEUE_SERIAL,之前只能NULL
dispatch_queue_t queue= dispatch_queue_create("LZQueueName", NULL);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_async(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
sleep(3);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
sleep(2);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
輸出:
2017-01-06 10:14:28.944 LZGCDTest[3019:60109] 主線程----<NSThread: 0x608000065440>{number = 1, name = main}
2017-01-06 10:14:33.331 LZGCDTest[3019:60150] 下載圖片1----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:36.404 LZGCDTest[3019:60150] 下載圖片2----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:38.478 LZGCDTest[3019:60150] 下載圖片3----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
可以看到, 任務(wù)沒(méi)有在主線程里執(zhí)行, 開(kāi)啟了一個(gè)新的線程, 雖然是異步, 但是隊(duì)列是串行隊(duì)列, 所以任務(wù)還是按照添加的順序, 順序執(zhí)行的;
測(cè)試四. 用異步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
同時(shí)開(kāi)啟多個(gè)子線程執(zhí)行任務(wù):
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//1.獲得全局的并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.添加任務(wù)到隊(duì)列中廊佩,就可以執(zhí)行任務(wù)
//異步函數(shù):具備開(kāi)啟新線程的能力
dispatch_async(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
sleep(3);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
sleep(2);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
});
輸出:
2017-01-06 10:18:12.210 LZGCDTest[3134:63076] 主線程----<NSThread: 0x60800006e700>{number = 1, name = main}
2017-01-06 10:18:12.211 LZGCDTest[3134:63122] 下載圖片1----<NSThread: 0x60000007c040>{number = 3, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63121] 下載圖片2----<NSThread: 0x6080002671c0>{number = 4, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63124] 下載圖片2----<NSThread: 0x60800026fe80>{number = 5, name = (null)}
可以看出, 這里開(kāi)啟了三個(gè)子線程來(lái)執(zhí)行任務(wù).
以上四個(gè)測(cè)試, 能夠看出同步, 異步, 串行, 并行之間的關(guān)系, 只有異步函數(shù)和并行隊(duì)列組合才能真正實(shí)現(xiàn)并發(fā)的效果.
測(cè)試五. 控制最大并發(fā)數(shù)
在進(jìn)行并發(fā)操作的時(shí)候, 如果任務(wù)過(guò)多, 開(kāi)啟很多線程, 會(huì)導(dǎo)致APP卡死. 所以, 我們要控制最大并發(fā)數(shù), 這就用到了信號(hào)量, 與之相關(guān)的三個(gè)函數(shù)為:
// value : 必須是大于等于0的數(shù), 否則會(huì)返回NULL
dispatch_semaphore_t
dispatch_semaphore_create(long value);
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
這里有一篇文章是介紹信號(hào)量的, 可參考:關(guān)于dispatch_semaphore的使用
一個(gè)應(yīng)用示例:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設(shè)置最大任務(wù)數(shù)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
// 執(zhí)行任務(wù)
NSLog(@"%d", i);
// 模擬任務(wù)時(shí)間, 休眠2s
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
這樣就會(huì), 每十個(gè)一組的進(jìn)行輸出...
四. 一些應(yīng)用
下面給出一些, GCD在編程中的常用示例:
1. 只執(zhí)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
這個(gè)同常用來(lái)創(chuàng)建單例, 能夠保證block內(nèi)的代碼只執(zhí)行一次.
2. 延遲執(zhí)行
dispatch_time_t time = dispatch_time ( DISPATCH_TIME_NOW , 3ull * NSEC_PER_SEC ) ;
dispatch_after ( time , dispatch_get_main_queue (),^{
NSLog ( @"waited at least three seconds." );
});
這里是延遲3s來(lái)執(zhí)行block內(nèi)的任務(wù);
3. 重復(fù)執(zhí)行
dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"重復(fù)執(zhí)行5次");
});
這個(gè)方法可以重復(fù)執(zhí)行某個(gè)任務(wù), 這里執(zhí)行5次輸出;
4. 指定執(zhí)行
如果, 你想某個(gè)任務(wù)在其他任務(wù)執(zhí)行之后再執(zhí)行, 或者必須某個(gè)任務(wù)執(zhí)行完,才能執(zhí)行下面的任務(wù), 可以使用這個(gè)函數(shù)dispatch_barrier_async :
dispatch_queue_t queue = dispatch_queue_create("LZQueueName", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
這里是使用的并發(fā)隊(duì)列異步執(zhí)行, 按說(shuō)應(yīng)該是互不影響的, 但是有了** dispatch_barrier_async** ,他的作用就是在他前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行.
輸出:
2017-01-06 10:33:03.063 LZGCDTest[3454:71023] dispatch_async1
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_async2
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_barrier_async
2017-01-06 10:33:10.151 LZGCDTest[3454:71026] dispatch_async3
5. 匯總dispatch_group_async
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽(tīng)一組任務(wù)是否完成粥惧,完成后得到通知執(zhí)行其他的操作。比如你執(zhí)行三個(gè)下載任務(wù),當(dāng)三個(gè)任務(wù)都下載完成后你才通知界面做相應(yīng)的刷新操作:
// 獲取全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 創(chuàng)建一個(gè)組
dispatch_group_t group = dispatch_group_create();
// 添加任務(wù)
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});
輸出:
2017-01-06 10:41:02.099 LZGCDTest[3668:76419] group2
2017-01-06 10:41:03.098 LZGCDTest[3668:76416] group3
2017-01-06 10:41:03.098 LZGCDTest[3668:76417] group1
2017-01-06 10:41:03.099 LZGCDTest[3668:76375] updateUi
6. 延長(zhǎng)后臺(tái)運(yùn)行時(shí)間
當(dāng)用戶使用Home鍵使APP后臺(tái)運(yùn)行, 一般只有最多5s的時(shí)間, 就會(huì)被系統(tǒng)殺死, 如果在這些時(shí)間里你不能完成一些操作, 例如清理緩存, 保存數(shù)據(jù), 就有可能造成數(shù)據(jù)丟失, 這時(shí)可以使用這個(gè)方法, 來(lái)使程序的后臺(tái)運(yùn)行時(shí)間延長(zhǎng)至10分鐘左右:
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
// 在這里加上你需要長(zhǎng)久運(yùn)行的代碼
[self endBackgroundUpdateTask];
}
- (void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}