文中較詳細(xì)介紹GCD隊(duì)列筹吐,各種GCD使用方法,實(shí)例如何使用Dispatch Source監(jiān)聽系統(tǒng)底層對象淘衙,分析不同鎖的性能對比,實(shí)例GCD死鎖情況昭躺。文中的Demo在這里 https://github.com/ming1016/GCDDemo 對著文章試著來調(diào)demo體會更深哦忌锯,細(xì)細(xì)嚼消化好:)
GCD(Grand Central Dispatch) 介紹
GCD屬于系統(tǒng)級的線程管理,在Dispatch queue中執(zhí)行需要執(zhí)行的任務(wù)性能非常的高领炫。GCD這塊已經(jīng)開源偶垮,地址http://libdispatch.macosforge.org。GCD中的FIFO隊(duì)列稱為dispatch queue帝洪,用來保證先進(jìn)來的任務(wù)先得到執(zhí)行似舵。
GCD概要
- 和operation queue一樣都是基于隊(duì)列的并發(fā)編程API,他們通過集中管理大家協(xié)同使用的線程池葱峡。
- 公開的5個(gè)不同隊(duì)列:運(yùn)行在主線程中的main queue砚哗,3個(gè)不同優(yōu)先級的后臺隊(duì)列(High Priority Queue,Default Priority Queue砰奕,Low Priority Queue)蛛芥,以及一個(gè)優(yōu)先級更低的后臺隊(duì)列Background Priority Queue(用于I/O)
- 可創(chuàng)建自定義隊(duì)列:串行或并列隊(duì)列。自定義一般放在Default Priority Queue和Main Queue里军援。
- 操作是在多線程上還是單線程主要是看隊(duì)列的類型和執(zhí)行方法仅淑,并行隊(duì)列異步執(zhí)行才能在多線程,并行隊(duì)列同步執(zhí)行就只會在主線程執(zhí)行了
基本概念
- 系統(tǒng)標(biāo)準(zhǔn)兩個(gè)隊(duì)列
//全局隊(duì)列胸哥,一個(gè)并行的隊(duì)列
dispatch_get_global_queue
//主隊(duì)列涯竟,主線程中的唯一隊(duì)列,一個(gè)串行隊(duì)列
dispatch_get_main_queue
- 自定義隊(duì)列
//串行隊(duì)列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行隊(duì)列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
- 同步異步線程創(chuàng)建
//同步線程
dispatch_sync(..., ^(block))
//異步線程
dispatch_async(..., ^(block))
隊(duì)列(dispatch queue)
- Serial:又叫private dispatch queues空厌,同時(shí)只執(zhí)行一個(gè)任務(wù)庐船。Serial queue常用于同步訪問特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個(gè)Serial queue時(shí)嘲更,雖然各自是同步筐钟,但serial queue之間是并發(fā)執(zhí)行。
- Main dispatch queue:全局可用的serial queue赋朦,在應(yīng)用程序主線程上執(zhí)行任務(wù)篓冲。
- Concurrent:又叫g(shù)lobal dispatch queue,可以并發(fā)的執(zhí)行多個(gè)任務(wù)北发,但執(zhí)行完成順序是隨機(jī)的。系統(tǒng)提供四個(gè)全局并發(fā)隊(duì)列喷屋,這四個(gè)隊(duì)列有這對應(yīng)的優(yōu)先級琳拨,用戶是不能夠創(chuàng)建全局隊(duì)列的,只能獲取屯曹。
dipatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
- user create queue:創(chuàng)建自己定義的隊(duì)列狱庇,可以用dispatch_queue_create函數(shù)惊畏,函數(shù)有兩個(gè)參數(shù),第一個(gè)自定義的隊(duì)列名密任,第二個(gè)參數(shù)是隊(duì)列類型颜启,默認(rèn)NULL或者DISPATCH_QUEUE_SERIAL的是串行浪讳,參數(shù)為DISPATCH_QUEUE_CONCURRENT為并行隊(duì)列缰盏。
dispatch_queue_t queue
queue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
- 自定義隊(duì)列的優(yōu)先級:可以通過dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法設(shè)置隊(duì)列的優(yōu)先級
//dipatch_queue_attr_make_with_qos_class
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr);
//dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要設(shè)置優(yōu)先級的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //參考優(yōu)先級
dispatch_set_target_queue(queue, referQueue); //設(shè)置queue和referQueue的優(yōu)先級一樣
- dispatch_set_target_queue:可以設(shè)置優(yōu)先級,也可以設(shè)置隊(duì)列層級體系淹遵,比如讓多個(gè)串行和并行隊(duì)列在統(tǒng)一一個(gè)串行隊(duì)列里串行執(zhí)行口猜,如下
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t firstQueue = dispatch_queue_create("com.starming.gcddemo.firstqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t secondQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(firstQueue, serialQueue);
dispatch_set_target_queue(secondQueue, serialQueue);
dispatch_async(firstQueue, ^{
NSLog(@"1");
[NSThread sleepForTimeInterval:3.f];
});
dispatch_async(secondQueue, ^{
NSLog(@"2");
[NSThread sleepForTimeInterval:2.f];
});
dispatch_async(secondQueue, ^{
NSLog(@"3");
[NSThread sleepForTimeInterval:1.f];
});
隊(duì)列類型
隊(duì)列默認(rèn)是串行的,如果設(shè)置改參數(shù)為NULL會按串行處理透揣,只能執(zhí)行一個(gè)單獨(dú)的block济炎,隊(duì)列也可以是并行的,同一時(shí)間執(zhí)行多個(gè)block
- (id)init;
{
self = [super init];
if (self != nil) {
NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self];
self.isolationQueue = dispatch_queue_create([label UTF8String], 0);
label = [NSString stringWithFormat:@"%@.work.%p", [self class], self];
self.workQueue = dispatch_queue_create([label UTF8String], 0);
}
return self;
}
5種隊(duì)列辐真,主隊(duì)列(main queue),四種通用調(diào)度隊(duì)列须尚,自己定制的隊(duì)列。四種通用調(diào)度隊(duì)列為
- QOS_CLASS_USER_INTERACTIVE:user interactive等級表示任務(wù)需要被立即執(zhí)行提供好的體驗(yàn)侍咱,用來更新UI耐床,響應(yīng)事件等。這個(gè)等級最好保持小規(guī)模放坏。
- QOS_CLASS_USER_INITIATED:user initiated等級表示任務(wù)由UI發(fā)起異步執(zhí)行咙咽。適用場景是需要及時(shí)結(jié)果同時(shí)又可以繼續(xù)交互的時(shí)候。
- QOS_CLASS_UTILITY:utility等級表示需要長時(shí)間運(yùn)行的任務(wù)淤年,伴有用戶可見進(jìn)度指示器钧敞。經(jīng)常會用來做計(jì)算,I/O麸粮,網(wǎng)絡(luò)溉苛,持續(xù)的數(shù)據(jù)填充等任務(wù)。這個(gè)任務(wù)節(jié)能弄诲。
- QOS_CLASS_BACKGROUND:background等級表示用戶不會察覺的任務(wù)愚战,使用它來處理預(yù)加載,或者不需要用戶交互和對時(shí)間不敏感的任務(wù)齐遵。
示例:后臺加載顯示圖片
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 將工作從主線程轉(zhuǎn)移到全局隊(duì)列中寂玲,這是dispatch_async調(diào)用,異步提交保證調(diào)用線程會繼續(xù)執(zhí)行下去梗摇,這樣viewDidLoad在主線程上能夠更早完成拓哟,
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(dispatch_get_main_queue()) { // 新圖完成,把一個(gè)閉包加入主線程用來更新UIImageView伶授,只有在主線程能操作UIKit断序。
self.fadeInNewImage(overlayImage) // 更新UI
}
}
}
何時(shí)使用何種隊(duì)列類型
- 主隊(duì)列(順序):隊(duì)列中有任務(wù)完成需要更新UI時(shí)流纹,dispatch_after在這種類型中使用。
- 并發(fā)隊(duì)列:用來執(zhí)行與UI無關(guān)的后臺任務(wù)违诗,dispatch_sync放在這里漱凝,方便等待任務(wù)完成進(jìn)行后續(xù)處理或和dispatch barrier同步。dispatch groups放在這里也不錯(cuò)诸迟。
- 自定義順序隊(duì)列:順序執(zhí)行后臺任務(wù)并追蹤它時(shí)茸炒。這樣做同時(shí)只有一個(gè)任務(wù)在執(zhí)行可以防止資源競爭。dipatch barriers解決讀寫鎖問題的放在這里處理亮蒋。dispatch groups也是放在這里扣典。
可以使用下面的方法簡化QoS等級參數(shù)的寫法
var GlobalMainQueue: dispatch_queue_t {
return dispatch_get_main_queue()
}
var GlobalUserInteractiveQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
}
var GlobalUserInitiatedQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
}
var GlobalUtilityQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)
}
var GlobalBackgroundQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
//使用起來就是這樣,易讀而且容易看出在使用哪個(gè)隊(duì)列
dispatch_async(GlobalUserInitiatedQueue) {
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(GlobalMainQueue) {
self.fadeInNewImage(overlayImage)
}
}
dispatch_once用法
dispatch_once_t要是全局或static變量慎玖,保證dispatch_once_t只有一份實(shí)例
+ (UIColor *)boringColor;
{
static UIColor *color;
//只運(yùn)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
});
return color;
}
dispatch_async
設(shè)計(jì)一個(gè)異步的API調(diào)用dispatch_async()贮尖,這個(gè)調(diào)用放在API的方法或函數(shù)中做。讓API的使用者設(shè)置一個(gè)回調(diào)處理隊(duì)列
- (void)processImage:(UIImage *)image completionHandler:(void(^)(BOOL success))handler;
{
dispatch_async(self.isolationQueue, ^(void){
// do actual processing here
dispatch_async(self.resultQueue, ^(void){
handler(YES);
});
});
}
可以避免界面會被一些耗時(shí)的操作卡死趁怔,比如讀取網(wǎng)絡(luò)數(shù)據(jù)湿硝,大數(shù)據(jù)IO,還有大量數(shù)據(jù)的數(shù)據(jù)庫讀寫润努,這時(shí)需要在另一個(gè)線程中處理关斜,然后通知主線程更新界面,GCD使用起來比NSThread和NSOperation方法要簡單方便铺浇。
//代碼框架
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗時(shí)的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});
//下載圖片的示例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
});
dispatch_after延后執(zhí)行
dispatch_after只是延時(shí)提交block痢畜,不是延時(shí)立刻執(zhí)行。
- (void)foo
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self bar];
});
}
范例鳍侣,實(shí)現(xiàn)一個(gè)推遲出現(xiàn)彈出框提示丁稀,比如說提示用戶評價(jià)等功能。
func showOrHideNavPrompt() {
let delayInSeconds = 1.0
let popTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(delayInSeconds * Double(NSEC_PER_SEC))) // 在這里聲明推遲的時(shí)間
dispatch_after(popTime, GlobalMainQueue) { // 等待delayInSeconds將閉包異步到主隊(duì)列
let count = PhotoManager.sharedManager.photos.count
if count > 0 {
self.navigationItem.prompt = nil
} else {
self.navigationItem.prompt = "Add photos with faces to Googlyify them!"
}
}
}
例子中的dispatch time的參數(shù)倚聚,可以先看看函數(shù)原型
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
第一個(gè)參數(shù)為DISPATCH_TIME_NOW表示當(dāng)前线衫。第二個(gè)參數(shù)的delta表示納秒,一秒對應(yīng)的納秒為1000000000惑折,系統(tǒng)提供了一些宏來簡化
#define NSEC_PER_SEC 1000000000ull //每秒有多少納秒
#define USEC_PER_SEC 1000000ull //每秒有多少毫秒
#define NSEC_PER_USEC 1000ull //每毫秒有多少納秒
這樣如果要表示一秒就可以這樣寫
dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
dispatch_barrier_async使用Barrier Task方法Dispatch Barrier解決多線程并發(fā)讀寫同一個(gè)資源發(fā)生死鎖
Dispatch Barrier確保提交的閉包是指定隊(duì)列中在特定時(shí)段唯一在執(zhí)行的一個(gè)授账。在所有先于Dispatch Barrier的任務(wù)都完成的情況下這個(gè)閉包才開始執(zhí)行。輪到這個(gè)閉包時(shí)barrier會執(zhí)行這個(gè)閉包并且確保隊(duì)列在此過程不會執(zhí)行其它任務(wù)惨驶。閉包完成后隊(duì)列恢復(fù)白热。需要注意dispatch_barrier_async只在自己創(chuàng)建的隊(duì)列上有這種作用,在全局并發(fā)隊(duì)列和串行隊(duì)列上粗卜,效果和dispatch_sync一樣
//創(chuàng)建隊(duì)列
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
//改變setter
- (void)setCount:(NSUInteger)count forKey:(NSString *)key
{
key = [key copy];
//確保所有barrier都是async異步的
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = @(count);
}
});
}
- (void)dispatchBarrierAsyncDemo {
//防止文件讀寫沖突屋确,可以創(chuàng)建一個(gè)串行隊(duì)列,操作都在這個(gè)隊(duì)列中進(jìn)行,沒有更新數(shù)據(jù)讀用并行乍恐,寫用串行。
dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.dataqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"read data 1");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 2");
});
//等待前面的都完成测砂,在執(zhí)行barrier后面的
dispatch_barrier_async(dataQueue, ^{
NSLog(@"write data 1");
[NSThread sleepForTimeInterval:1];
});
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"read data 3");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 4");
});
}
swift示例
//使用dispatch_queue_create初始化一個(gè)并發(fā)隊(duì)列茵烈。第一個(gè)參數(shù)遵循反向DNS命名習(xí)慣,方便描述砌些,第二個(gè)參數(shù)是指出是并發(fā)還是順序呜投。
private let concurrentPhotoQueue = dispatch_queue_create(
"com.raywenderlich.GooglyPuff.photoQueue", DISPATCH_QUEUE_CONCURRENT)
func addPhoto(photo: Photo) {
dispatch_barrier_async(concurrentPhotoQueue) { // 將寫操作加入到自定義的隊(duì)列。開始執(zhí)行時(shí)這個(gè)就是隊(duì)列中唯一的一個(gè)在執(zhí)行的任務(wù)存璃。
self._photos.append(photo) // barrier能夠保障不會和其他任務(wù)同時(shí)進(jìn)行仑荐。
dispatch_async(GlobalMainQueue) { // 涉及到UI所以這個(gè)通知應(yīng)該在主線程中,所以分派另一個(gè)異步任務(wù)到主隊(duì)列中纵东。
self.postContentAddedNotification()
}
}
}
//上面是解決了寫可能發(fā)生死鎖粘招,下面是使用dispatch_sync解決讀時(shí)可能會發(fā)生的死鎖。
var photos: [Photo] {
var photosCopy: [Photo]!
dispatch_sync(concurrentPhotoQueue) { // 同步調(diào)度到concurrentPhotoQueue隊(duì)列執(zhí)行讀操作
photosCopy = self._photos // 保存
}
return photosCopy
}
//這樣讀寫問題都解決了偎球。
都用異步處理避免死鎖洒扎,異步的缺點(diǎn)在于調(diào)試不方便,但是比起同步容易產(chǎn)生死鎖這個(gè)副作用還算小的衰絮。
dispatch_apply進(jìn)行快速迭代
類似for循環(huán)袍冷,但是在并發(fā)隊(duì)列的情況下dispatch_apply會并發(fā)執(zhí)行block任務(wù)。
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
// Do something with x and y here
}
}
//因?yàn)榭梢圆⑿袌?zhí)行猫牡,所以使用dispatch_apply可以運(yùn)行的更快
- (void)dispatchApplyDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentQueue, ^(size_t i) {
NSLog(@"%zu",i);
});
NSLog(@"The end"); //這里有個(gè)需要注意的是胡诗,dispatch_apply這個(gè)是會阻塞主線程的。這個(gè)log打印會在dispatch_apply都結(jié)束后才開始執(zhí)行
}
dispatch_apply能避免線程爆炸淌友,因?yàn)镚CD會管理并發(fā)
- (void)dealWiththreadWithMaybeExplode:(BOOL)explode {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
if (explode) {
//有問題的情況煌恢,可能會死鎖
for (int i = 0; i < 999 ; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"wrong %d",i);
//do something hard
});
}
} else {
//會優(yōu)化很多,能夠利用GCD管理
dispatch_apply(999, concurrentQueue, ^(size_t i){
NSLog(@"correct %zu",i);
//do something hard
});
}
}
示例:
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
var storedError: NSError!
var downloadGroup = dispatch_group_create()
let addresses = [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) {
i in
let index = Int(i)
let address = addresses[index]
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup)
let photo = DownloadPhoto(url: url!) {
image, error in
if let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) {
if let completion = completion {
completion(error: storedError)
}
}
}
Block組合Dispatch_groups
dispatch groups是專門用來監(jiān)視多個(gè)異步任務(wù)亩进。dispatch_group_t實(shí)例用來追蹤不同隊(duì)列中的不同任務(wù)症虑。
當(dāng)group里所有事件都完成GCD API有兩種方式發(fā)送通知,第一種是dispatch_group_wait归薛,會阻塞當(dāng)前進(jìn)程谍憔,等所有任務(wù)都完成或等待超時(shí)。第二種方法是使用dispatch_group_notify主籍,異步執(zhí)行閉包习贫,不會阻塞。
第一種使用dispatch_group_wait的swift的例子:
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
dispatch_async(GlobalUserInitiatedQueue) { // 因?yàn)閐ispatch_group_wait會租塞當(dāng)前進(jìn)程千元,所以要使用dispatch_async將整個(gè)方法要放到后臺隊(duì)列才能夠保證主線程不被阻塞
var storedError: NSError!
var downloadGroup = dispatch_group_create() // 創(chuàng)建一個(gè)dispatch group
for address in [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
{
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup) // dispatch_group_enter是通知dispatch group任務(wù)開始了苫昌,dispatch_group_enter和dispatch_group_leave是成對調(diào)用,不然程序就崩潰了幸海。
let photo = DownloadPhoto(url: url!) {
image, error in
if let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup) // 保持和dispatch_group_enter配對祟身。通知任務(wù)已經(jīng)完成
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // dispatch_group_wait等待所有任務(wù)都完成直到超時(shí)奥务。如果任務(wù)完成前就超時(shí)了,函數(shù)會返回一個(gè)非零值袜硫,可以通過返回值判斷是否超時(shí)氯葬。也可以用DISPATCH_TIME_FOREVER表示一直等。
dispatch_async(GlobalMainQueue) { // 這里可以保證所有圖片任務(wù)都完成婉陷,然后在main queue里加入完成后要處理的閉包帚称,會在main queue里執(zhí)行。
if let completion = completion { // 執(zhí)行閉包內(nèi)容
completion(error: storedError)
}
}
}
}
oc例子
- (void)dispatchGroupWaitDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//在group中添加隊(duì)列的block
dispatch_group_async(group, concurrentQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"go on");
}
第二種使用dispatch_group_notify的swift的例子:
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
// 不用加dispatch_async秽澳,因?yàn)闆]有阻塞主進(jìn)程
var storedError: NSError!
var downloadGroup = dispatch_group_create()
for address in [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
{
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup)
let photo = DownloadPhoto(url: url!) {
image, error in
if let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) { // dispatch_group_notify和dispatch_group_wait的區(qū)別就是是異步執(zhí)行閉包的闯睹,當(dāng)dispatch groups中沒有剩余的任務(wù)時(shí)閉包才執(zhí)行。這里是指明在主隊(duì)列中執(zhí)行担神。
if let completion = completion {
completion(error: storedError)
}
}
}
oc例子
//dispatch_group_notify
- (void)dispatchGroupNotifyDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
NSLog(@"can continue");
}
//dispatch_group_wait
- (void)dispatchGroupWaitDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//在group中添加隊(duì)列的block
dispatch_group_async(group, concurrentQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"can continue");
}
如何對現(xiàn)有API使用dispatch_group_t
//給Core Data的-performBlock:添加groups楼吃。組合完成任務(wù)后使用dispatch_group_notify來運(yùn)行一個(gè)block即可。
- (void)withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block
{
if (group == NULL) {
[self performBlock:block];
} else {
dispatch_group_enter(group);
[self performBlock:^(){
block();
dispatch_group_leave(group);
}];
}
}
//NSURLConnection也可以這樣做
+ (void)withGroup:(dispatch_group_t)group
sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
{
if (group == NULL) {
[self sendAsynchronousRequest:request
queue:queue
completionHandler:handler];
} else {
dispatch_group_enter(group);
[self sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
handler(response, data, error);
dispatch_group_leave(group);
}];
}
}
注意事項(xiàng)
- dispatch_group_async等價(jià)于dispatch_group_enter() 和 dispatch_group_leave()的組合妄讯。
- dispatch_group_enter() 必須運(yùn)行在 dispatch_group_leave() 之前所刀。
- dispatch_group_enter() 和 dispatch_group_leave() 需要成對出現(xiàn)的
Dispatch Block
隊(duì)列執(zhí)行任務(wù)都是block的方式,
- 創(chuàng)建block
- (void)createDispatchBlock {
//normal way
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"run block");
});
dispatch_async(concurrentQueue, block);
//QOS way
dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
NSLog(@"run qos block");
});
dispatch_async(concurrentQueue, qosBlock);
}
- dispatch_block_wait:可以根據(jù)dispatch block來設(shè)置等待時(shí)間捞挥,參數(shù)DISPATCH_TIME_FOREVER會一直等待block結(jié)束
- (void)dispatchBlockWaitDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"star");
[NSThread sleepForTimeInterval:5.f];
NSLog(@"end");
});
dispatch_async(serialQueue, block);
//設(shè)置DISPATCH_TIME_FOREVER會一直等到前面任務(wù)都完成
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"ok, now can go on");
}
- dispatch_block_notify:可以監(jiān)視指定dispatch block結(jié)束浮创,然后再加入一個(gè)block到隊(duì)列中。三個(gè)參數(shù)分別為砌函,第一個(gè)是需要監(jiān)視的block斩披,第二個(gè)參數(shù)是需要提交執(zhí)行的隊(duì)列,第三個(gè)是待加入到隊(duì)列中的block
- (void)dispatchBlockNotifyDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t firstBlock = dispatch_block_create(0, ^{
NSLog(@"first block start");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"first block end");
});
dispatch_async(serialQueue, firstBlock);
dispatch_block_t secondBlock = dispatch_block_create(0, ^{
NSLog(@"second block run");
});
//first block執(zhí)行完才在serial queue中執(zhí)行second block
dispatch_block_notify(firstBlock, serialQueue, secondBlock);
}
- dispatch_block_cancel:iOS8后GCD支持對dispatch block的取消
- (void)dispatchBlockCancelDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t firstBlock = dispatch_block_create(0, ^{
NSLog(@"first block start");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"first block end");
});
dispatch_block_t secondBlock = dispatch_block_create(0, ^{
NSLog(@"second block run");
});
dispatch_async(serialQueue, firstBlock);
dispatch_async(serialQueue, secondBlock);
//取消secondBlock
dispatch_block_cancel(secondBlock);
}
使用dispatch block object(調(diào)度塊)在任務(wù)執(zhí)行前進(jìn)行取消
dispatch block object可以為隊(duì)列中的對象設(shè)置
示例讹俊,下載圖片中途進(jìn)行取消
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
var storedError: NSError!
let downloadGroup = dispatch_group_create()
var addresses = [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
addresses += addresses + addresses // 擴(kuò)展address數(shù)組垦沉,復(fù)制3份
var blocks: [dispatch_block_t] = [] // 一個(gè)保存block的數(shù)組
for i in 0 ..< addresses.count {
dispatch_group_enter(downloadGroup)
let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) { // 創(chuàng)建一個(gè)block,block的標(biāo)志是DISPATCH_BLOCK_INHERIT_QOS_CLASS
let index = Int(i)
let address = addresses[index]
let url = NSURL(string: address)
let photo = DownloadPhoto(url: url!) {
image, error in
if let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
blocks.append(block)
dispatch_async(GlobalMainQueue, block) // 把這個(gè)block放到GlobalMainQueue上異步調(diào)用仍劈。因?yàn)槿株?duì)列是一個(gè)順序隊(duì)列所以方便取消對象block厕倍,同時(shí)可以保證下載任務(wù)在downloadPhotosWithCompletion返回后才開始執(zhí)行。
}
for block in blocks[3 ..< blocks.count] {
let cancel = arc4random_uniform(2) // 隨機(jī)返回一個(gè)整數(shù)贩疙,會返回0或1
if cancel == 1 {
dispatch_block_cancel(block) // 如果是1就取消block讹弯,這個(gè)只能發(fā)生在block還在隊(duì)列中并沒有開始的情況下。因?yàn)榘裝lock已經(jīng)放到了GlobalMainQueue中这溅,所以這個(gè)地方會先執(zhí)行组民,執(zhí)行完了才會執(zhí)行block。
dispatch_group_leave(downloadGroup) // 因?yàn)橐呀?jīng)dispatch_group_enter了悲靴,所以取消時(shí)也要將其都leave掉臭胜。
}
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) {
if let completion = completion {
completion(error: storedError)
}
}
}
Dispatch IO 文件操作
dispatch io讀取文件的方式類似于下面的方式,多個(gè)線程去讀取文件的切片數(shù)據(jù),對于大的數(shù)據(jù)文件這樣會比單線程要快很多耸三。
dispatch_async(queue,^{/*read 0-99 bytes*/});
dispatch_async(queue,^{/*read 100-199 bytes*/});
dispatch_async(queue,^{/*read 200-299 bytes*/});
- dispatch_io_create:創(chuàng)建dispatch io
- dispatch_io_set_low_water:指定切割文件大小
- dispatch_io_read:讀取切割的文件然后合并乱陡。
蘋果系統(tǒng)日志API里用到了這個(gè)技術(shù),可以在這里查看:https://github.com/Apple-FOSS-Mirror/Libc/blob/2ca2ae74647714acfc18674c3114b1a5d3325d7d/gen/asl.c
pipe_q = dispatch_queue_create("PipeQ", NULL);
//創(chuàng)建
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
//設(shè)置切割大小
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0)
{
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
//對每次切塊數(shù)據(jù)的處理
const char *bytes = NULL;
char *encoded;
uint32_t eval;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
free(encoded);
eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
_asl_send_message(NULL, eval, aux, NULL);
asl_msg_release(aux);
dispatch_release(md);
}
}
if (done)
{
//semaphore +1使得不需要再等待繼續(xù)執(zhí)行下去仪壮。
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});
Dispatch Source 用GCD監(jiān)視進(jìn)程
Dispatch Source用于監(jiān)聽系統(tǒng)的底層對象蛋褥,比如文件描述符,Mach端口睛驳,信號量等。主要處理的事件如下表
方法 | 說明 |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 數(shù)據(jù)增加 |
DISPATCH_SOURCE_TYPE_DATA_OR | 數(shù)據(jù)OR |
DISPATCH_SOURCE_TYPE_MACH_SEND | Mach端口發(fā)送 |
DISPATCH_SOURCE_TYPE_MACH_RECV | Mach端口接收 |
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | 內(nèi)存情況 |
DISPATCH_SOURCE_TYPE_PROC | 進(jìn)程事件 |
DISPATCH_SOURCE_TYPE_READ | 讀數(shù)據(jù) |
DISPATCH_SOURCE_TYPE_SIGNAL | 信號 |
DISPATCH_SOURCE_TYPE_TIMER | 定時(shí)器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件系統(tǒng)變化 |
DISPATCH_SOURCE_TYPE_WRITE | 文件寫入 |
方法
- dispatch_source_create:創(chuàng)建dispatch source膜廊,創(chuàng)建后會處于掛起狀態(tài)進(jìn)行事件接收乏沸,需要設(shè)置事件處理handler進(jìn)行事件處理。
- dispatch_source_set_event_handler:設(shè)置事件處理handler
- dispatch_source_set_cancel_handler:事件取消handler爪瓜,就是在dispatch source釋放前做些清理的事蹬跃。
- dispatch_source_cancel:關(guān)閉dispatch source,設(shè)置的事件處理handler不會被執(zhí)行铆铆,已經(jīng)執(zhí)行的事件handler不會取消蝶缀。
NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
if (mail == nil) {
return;
}
pid_t const pid = mail.processIdentifier;
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(self.source, ^(){
NSLog(@"Mail quit.");
});
//在事件源傳到你的事件處理前需要調(diào)用dispatch_resume()這個(gè)方法
dispatch_resume(self.source);
監(jiān)視文件夾內(nèi)文件變化
NSURL *directoryURL; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
char buffer[80];
strerror_r(errno, buffer, sizeof(buffer));
NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
unsigned long const data = dispatch_source_get_data(source);
if (data & DISPATCH_VNODE_WRITE) {
NSLog(@"The directory changed.");
}
if (data & DISPATCH_VNODE_DELETE) {
NSLog(@"The directory has been deleted.");
}
});
dispatch_source_set_cancel_handler(source, ^(){
close(fd);
});
self.source = source;
dispatch_resume(self.source);
//還要注意需要用DISPATCH_VNODE_DELETE 去檢查監(jiān)視的文件或文件夾是否被刪除,如果刪除了就停止監(jiān)聽
NSTimer在主線程的runloop里會在runloop切換其它模式時(shí)停止薄货,這時(shí)就需要手動在子線程開啟一個(gè)模式為NSRunLoopCommonModes的runloop翁都,如果不想開啟一個(gè)新的runloop可以用不跟runloop關(guān)聯(lián)的dispatch source timer,如下谅猾。
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
NSLog(@"Time flies.");
});
dispatch_time_t start
dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC);
self.source = source;
dispatch_resume(self.source);
Dispatch Semaphore和的介紹
另外一種保證同步的方法柄慰。使用dispatch_semaphore_signal加1dispatch_semaphore_wait減1,為0時(shí)等待的設(shè)置方式來達(dá)到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題税娜。
//dispatch semaphore
- (void)dispatchSemaphoreDemo {
//創(chuàng)建semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"semaphore +1");
dispatch_semaphore_signal(semaphore); //+1 semaphore
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"continue");
}
鎖
這里簡單介紹下iOS中常用的各種鎖和他們的性能坐搔。
- NSRecursiveLock:遞歸鎖,可以在一個(gè)線程中反復(fù)獲取鎖不會造成死鎖敬矩,這個(gè)過程會記錄獲取鎖和釋放鎖的次數(shù)來達(dá)到何時(shí)釋放的作用概行。
- NSDistributedLock:分布鎖,基于文件方式的鎖機(jī)制弧岳,可以跨進(jìn)程訪問凳忙。
- NSConditionLock:條件鎖,用戶定義條件禽炬,確保一個(gè)線程可以獲取滿足一定條件的鎖消略。因?yàn)榫€程間競爭會涉及到條件鎖檢測,系統(tǒng)調(diào)用上下切換頻繁導(dǎo)致耗時(shí)是幾個(gè)鎖里最長的瞎抛。
- OSSpinLock:自旋鎖艺演,不進(jìn)入內(nèi)核,減少上下文切換,性能最高胎撤,但搶占多時(shí)會占用較多cpu晓殊,好點(diǎn)多,這時(shí)使用pthread_mutex較好伤提。
- pthread_mutex_t:同步鎖基于C語言巫俺,底層api性能高,使用方法和其它的類似肿男。
- @synchronized:更加簡單介汹。
dispatch_suspend和dispatch_resume掛起和恢復(fù)隊(duì)列
dispatch_suspend這里掛起不會暫停正在執(zhí)行的block,只是能夠暫停還沒執(zhí)行的block舶沛。
dispatch_set_context和dispatch_get_context
GCD深入操作
- 緩沖區(qū):dispatch_data_t基于零碎的內(nèi)存區(qū)域嘹承,使用dispatch_data_apply來遍歷,還可以用dispatch_data_create_subrange來創(chuàng)建一個(gè)不做任何拷貝的子區(qū)域
- I/O調(diào)度:使用GCD提供的dispatch_io_read如庭,dispatch_io_write和dispatch_io_close
- 測試:使用dispatch_benchmark小工具
- 原子操作: libkern/OSAtomic.h里可以查看那些函數(shù)叹卷,用于底層多線程編程。
GCD死鎖
當(dāng)前串行隊(duì)列里面同步執(zhí)行當(dāng)前串行隊(duì)列就會死鎖坪它,解決的方法就是將同步的串行隊(duì)列放到另外一個(gè)線程就能夠解決骤竹。
- (void)deadLockCase1 {
NSLog(@"1");
//主隊(duì)列的同步線程,按照FIFO的原則(先入先出)往毡,2排在3后面會等3執(zhí)行完蒙揣,但因?yàn)橥骄€程,3又要等2執(zhí)行完开瞭,相互等待成為死鎖鸣奔。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
- (void)deadLockCase2 {
NSLog(@"1");
//3會等2,因?yàn)?在全局并行隊(duì)列里惩阶,不需要等待3挎狸,這樣2執(zhí)行完回到主隊(duì)列,3就開始執(zhí)行
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
}
- (void)deadLockCase3 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
//串行隊(duì)列里面同步一個(gè)串行隊(duì)列就會死鎖
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)deadLockCase4 {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
//將同步的串行隊(duì)列放到另外一個(gè)線程就能夠解決
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)deadLockCase5 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
//回到主線程發(fā)現(xiàn)死循環(huán)后面就沒法執(zhí)行了
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
});
NSLog(@"4");
//死循環(huán)
while (1) {
//
}
}
GCD實(shí)際使用
FMDB如何使用dispatch_queue_set_specific和dispatch_get_specific來防止死鎖
作用類似objc_setAssociatedObject跟objc_getAssociatedObject
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//創(chuàng)建串行隊(duì)列断楷,所有數(shù)據(jù)庫的操作都在這個(gè)隊(duì)列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//標(biāo)記隊(duì)列
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
//檢查是否是同一個(gè)隊(duì)列來避免死鎖的方法
- (void)inDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}
iOS系統(tǒng)版本新特性
iOS8
iOS8新加了一個(gè)功能叫Quality of Service(QoS)锨匆,里面提供了一下幾個(gè)更容易理解的枚舉名來使用user interactive,user initiated冬筒,utility和background恐锣。下面的表做了對比
Global queue | Corresponding QoS class | 說明 |
---|---|---|
Main thread | NSQualityOfServiceUserInteractive | UI相關(guān),交互等 |
DISPATCH_QUEUE_PRIORITY_HIGH | NSQualityOfServiceUserInitiated | 用戶發(fā)起需要馬上得到結(jié)果進(jìn)行后續(xù)任務(wù) |
DISPATCH_QUEUE_PRIORITY_DEFAULT | NSQualityOfServiceDefault | 默認(rèn)的不應(yīng)該使用這個(gè)設(shè)置任務(wù) |
DISPATCH_QUEUE_PRIORITY_LOW | NSQualityOfServiceUtility | 花費(fèi)時(shí)間稍多比如下載舞痰,需要幾秒或幾分鐘的 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | NSQualityOfServiceBackground | 不可見在后臺的操作可能需要好幾分鐘甚至幾小時(shí)的 |
參考資料
WWDC
- Building Responsive and Efficient Apps with GCD:https://developer.apple.com/videos/play/wwdc2015-718/