在日常的
iOS
開發(fā)中胡陪,關(guān)于多線程使用的較多的就是Grand Central Dispatch(GCD)
了绰更,GCD
會自動利用更多的CPU
內(nèi)核,會自動管理線程的生命周期,總之GCD
的好處還是非常之多的,下面就對GCD
的使用進(jìn)行一個(gè)匯總曹抬。
GCD 的創(chuàng)建
GCD
中兩個(gè)核心概念:隊(duì)列
和 任務(wù)
,一般基本的用法只需要創(chuàng)建這兩步即可急鳄。
1. 隊(duì)列的創(chuàng)建
隊(duì)列有兩種方式:串行隊(duì)列
和 并發(fā)隊(duì)列
谤民。
-
串行隊(duì)列(Serial Dispatch Queue)
:讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行堰酿,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)张足。
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
-
并發(fā)隊(duì)列(Concurrent Dispatch Queue)
:可以讓多個(gè)任務(wù)同時(shí)一起執(zhí)行触创,但是并發(fā)隊(duì)列只有在異步(dispatch_async)
函數(shù)下才有效。
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
2. 任務(wù)的創(chuàng)建
執(zhí)行任務(wù)也有兩種方式:同步執(zhí)行
和 異步執(zhí)行
为牍。
-
同步執(zhí)行(sync)
:等待線程任務(wù)完成之后哼绑,才會進(jìn)行接下來的操作。
dispatch_sync(queue, ^ {
NSLog(@"同步執(zhí)行的任務(wù)");
});
-
異步執(zhí)行(async)
:線程任務(wù)單獨(dú)執(zhí)行碉咆,不會影響接下來的操作抖韩。
dispatch_async(queue, ^ {
NSLog(@"異步執(zhí)行的任務(wù)");
});
GCD 的使用
在 iOS
開發(fā)過程中,我們一般在 主線程
里邊進(jìn)行 UI
刷新疫铜,例如:點(diǎn)擊帽蝶、滾動、拖拽
等事件块攒,而耗時(shí)的操作則會放在 子線程
里邊進(jìn)行。而當(dāng)我們有時(shí)候在 子線程
完成了耗時(shí)操作時(shí)佃乘,需要回到主線程囱井,那么就用到了下面這個(gè)方法。
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"回到主線程");
});
1. 串行隊(duì)列 + 同步執(zhí)行
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 1 回到主線程");
});
});
dispatch_sync(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 2 回到主線程");
});
});
NSLog(@"----------- 結(jié)束 -----------");
因?yàn)?串行隊(duì)列
是按順序執(zhí)行任務(wù)趣避,而 同步任務(wù)
又是等待線程任務(wù)完成之后庞呕,才會進(jìn)行接下來的操作,所以基本和不使用多線程的效果是相同的程帕,下面是打印結(jié)果:
----------- 開始 -----------
任務(wù) 1
任務(wù) 1
任務(wù) 1
任務(wù) 2
任務(wù) 2
任務(wù) 2
----------- 結(jié)束 -----------
任務(wù) 1 回到主線程
任務(wù) 2 回到主線程
2. 串行隊(duì)列 + 異步執(zhí)行
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 1 回到主線程");
});
});
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 2 回到主線程");
});
});
NSLog(@"----------- 結(jié)束 -----------");
由于 異步任務(wù)
是單獨(dú)執(zhí)行住练,所以不會影響到正常代碼的執(zhí)行,所以明顯就和上面有區(qū)別了愁拭,下面是打印結(jié)果:
----------- 開始 -----------
----------- 結(jié)束 -----------
任務(wù) 1
任務(wù) 1
任務(wù) 1
任務(wù) 2
任務(wù) 2
任務(wù) 2
任務(wù) 1 回到主線程
任務(wù) 2 回到主線程
3. 并發(fā)隊(duì)列 + 異步執(zhí)行
由于 并發(fā)隊(duì)列
在 同步任務(wù)
中是無效的讲逛,并發(fā)隊(duì)列 + 同步執(zhí)行
和 串行隊(duì)列 + 同步執(zhí)行
沒有任何區(qū)別,所以跳過直接看 并發(fā)隊(duì)列 + 異步執(zhí)行
岭埠。
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 1 回到主線程");
});
});
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任務(wù) 2 回到主線程");
});
});
NSLog(@"----------- 結(jié)束 -----------");
由于 并發(fā)隊(duì)列
是多個(gè)任務(wù)同時(shí)執(zhí)行的盏混,所以可以看到 任務(wù)1
和 任務(wù)2
基本是同時(shí)執(zhí)行的,下面是打印結(jié)果:
----------- 開始 -----------
----------- 結(jié)束 -----------
任務(wù) 1
任務(wù) 2
任務(wù) 1
任務(wù) 2
任務(wù) 1
任務(wù) 2
任務(wù) 1 回到主線程
任務(wù) 2 回到主線程
4. notification 隊(duì)列組(異步 + 并發(fā))
上面 并發(fā)隊(duì)列 + 異步執(zhí)行
是各自任務(wù)完成后各自回到 主線程
惜论。但是有時(shí)候我們會有這樣的需求:分別異步執(zhí)行兩個(gè)耗時(shí)操作许赃,然后當(dāng)兩個(gè)耗時(shí)操作都執(zhí)行完畢后再回到 主線程
執(zhí)行操作,這時(shí)候我們可以用到 GCD
的 notification
隊(duì)列組馆类。
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 1");
}
});
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 2");
}
});
/* 在組內(nèi)的任務(wù)都運(yùn)行完了之后再回到主線程 */
dispatch_group_notify(group, dispatch_get_main_queue(), ^ {
NSLog(@"回到主線程");
});
NSLog(@"----------- 結(jié)束 -----------");
notification
隊(duì)列組會等到兩個(gè)任務(wù)都執(zhí)行完才會回到主線程混聊,下面是打印結(jié)果:
----------- 開始 -----------
----------- 結(jié)束 -----------
任務(wù) 1
任務(wù) 2
任務(wù) 1
任務(wù) 2
任務(wù) 1
任務(wù) 2
回到主線程
5. wait 隊(duì)列組(同步 + 并發(fā))
上面說過 并發(fā)隊(duì)列
在 同步任務(wù)
中時(shí)無效的,但是 wait
隊(duì)列組就能實(shí)現(xiàn)這個(gè) 同步 + 并發(fā)
效果乾巧。
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 1");
}
});
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任務(wù) 2");
}
});
/* 等待組內(nèi)的任務(wù)都完成后句喜,繼續(xù)運(yùn)行 */
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
/* 回到主線程 */
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"回到主線程");
});
NSLog(@"----------- 結(jié)束 -----------");
可以看到任務(wù)是并發(fā)執(zhí)行的预愤,并且 結(jié)束
的打印在任務(wù)完成之后,下面是打印結(jié)果:
----------- 開始 -----------
任務(wù) 2
任務(wù) 1
任務(wù) 2
任務(wù) 1
任務(wù) 2
任務(wù) 1
----------- 結(jié)束 -----------
回到主線程
6. 延時(shí)執(zhí)行的代碼
當(dāng)我們需要延遲執(zhí)行一段代碼時(shí)藤滥,就可以用到 GCD
的 dispatch_after
方法鳖粟。
NSLog(@"----------- 開始 -----------");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
NSLog(@"延遲 3 秒后執(zhí)行的代碼"); // 更改秒數(shù)只要更改上面的數(shù)字 3 即可
});
NSLog(@"----------- 結(jié)束 -----------");
這個(gè)方法是異步執(zhí)行的,所以不會卡住界面拙绊,從時(shí)間可以看出延遲了 3
秒后才打印的向图,下面是打印結(jié)果:
2017-06-09 12:28:44.307 ----------- 開始 -----------
2017-06-09 12:28:44.307 ----------- 結(jié)束 -----------
2017-06-09 12:28:47.560 延遲 3 秒后執(zhí)行的代碼
7. 只執(zhí)行一次的代碼
使用下面方法執(zhí)行的代碼,在整個(gè)程序運(yùn)行過程中标沪,只會被執(zhí)行一次榄攀,執(zhí)行過一次以后就再也不會被執(zhí)行,使用的是 GCD
的 dispatch_once
方法金句。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^ {
NSLog(@"只執(zhí)行一次的代碼");
});
8. 同時(shí)執(zhí)行多次的代碼
通常我們會用 for
循環(huán)來使代碼執(zhí)行多次檩赢,但是 GCD
給我們提供了快速迭代的方法 dispatch_apply
,使我們可以將代碼執(zhí)行多次违寞。
NSLog(@"----------- 開始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"同時(shí)執(zhí)行多次的代碼 %zd",index); // 更改上面的數(shù)字 5 即可更改執(zhí)行次數(shù)
});
NSLog(@"----------- 結(jié)束 -----------");
dispatch_apply
和 for
循環(huán)是有區(qū)別的贞瞒,for
循環(huán)是按順序依次執(zhí)行多次,而 dispatch_apply
是同時(shí)執(zhí)行多次趁曼,可以看到下面的 5
次幾乎是同一時(shí)間執(zhí)行的军浆,下面是打印結(jié)果:
----------- 開始 -----------
同時(shí)執(zhí)行多次的代碼 0
同時(shí)執(zhí)行多次的代碼 2
同時(shí)執(zhí)行多次的代碼 3
同時(shí)執(zhí)行多次的代碼 1
同時(shí)執(zhí)行多次的代碼 4
----------- 結(jié)束 -----------
9. 定時(shí)器
一般情況下,都是使用 NSTimer
來實(shí)現(xiàn)定時(shí)器挡闰,但是 GCD
其實(shí)也是可以實(shí)現(xiàn)定時(shí)器功能的乒融,并且 GCD
定時(shí)器不受 RunLoop
約束,比 NSTimer
更加準(zhǔn)時(shí)摄悯。
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer; // 定時(shí)器對象
@end
NSLog(@"----------- 開始 -----------");
__block NSInteger time = 0;
/* 獲得隊(duì)列 */
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/* 創(chuàng)建定時(shí)器(dispatch_source_t 本質(zhì)還是一個(gè) OC 對象) */
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/* 設(shè)置定時(shí)器屬性 */
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); // 1 秒后開始執(zhí)行
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); // 每隔 1 秒執(zhí)行一次
dispatch_source_set_timer(self.timer, start, interval, 0);
/* 回調(diào) */
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"計(jì)時(shí) %ld 秒", ++time);
if (time == 5) {
/* 取消定時(shí)器 */
dispatch_cancel(self.timer);
self.timer = nil;
}
});
/* 啟動定時(shí)器 */
dispatch_resume(self.timer);
NSLog(@"----------- 結(jié)束 -----------");
下面是打印結(jié)果:
2017-06-09 14:55:37.894 ----------- 開始 -----------
2017-06-09 14:55:37.894 ----------- 結(jié)束 -----------
2017-06-09 14:55:38.896 計(jì)時(shí) 1 秒
2017-06-09 14:55:39.895 計(jì)時(shí) 2 秒
2017-06-09 14:55:40.895 計(jì)時(shí) 3 秒
2017-06-09 14:55:41.895 計(jì)時(shí) 4 秒
2017-06-09 14:55:42.895 計(jì)時(shí) 5 秒
用法大概就這么多赞季,有疑問或者不對的地方可以在下面留言,有需要的可以收藏一下奢驯。
將來的你申钩,一定會感激現(xiàn)在拼命的自己,愿自己與讀者的開發(fā)之路無限美好叨橱。