队列有三种:
1. main thread
2. serial thread
3. concurrent thread
有两种方法向dispatch queue(调度队列)提交任务:
- Block对象
- C 函数
之前讲过Block对象,现在来说说C函数。
必需提供给GCD的C函数需要是despatch_function_t类型。它在Apple libraries里边是这样被定义的:
typedef void (*dispatch_function_t)(void*)
解决方案:
使用dispatch_get_main_queue方法
讨论:
UI相关任务是执行在主线程上的,所以main queue是唯一选择。我们有两种方式来分发到主队列:
以上两种方法都是异步方法,dispatch_sync方法不能在主线程上使用,因为它会阻塞进程,导致App进入死锁。所有通过GCD提交到主队列的任务都应该是异步提交的。
它有两个参数:
来一发无参数无返回值的小例子:
使用Block
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"GCD" message:@"GCD is amazing!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self showViewController:alertController sender:nil];
});
使用C函数,我们可以提交一个指向application定义的上下文的指针
以下这些内容都写在AppDelegate.m中
typedef struct{
char *title;
char *message;
char *cancelButtonTitle;
}AlertViewData;
void displayAlertView(void *paramContext){
AlertViewData *alertData = (AlertViewData *)paramContext;
NSString *title = [NSString stringWithUTF8String:alertData->title];
NSString *message = [NSString stringWithUTF8String:alertData->message];
NSString *cancelButtonTitle = [NSString stringWithUTF8String:alertData->cancelButtonTitle];
[[[UIAlertView alloc]initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]show];
free(alertData);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
AlertViewData *context = (AlertViewData*)malloc(sizeof(AlertViewData));
if (context) {
context->title = "GCD";
context->message = "GCD is amazing!";
context->cancelButtonTitle = "OK";
dispatch_async_f(mainQueue, (void *)context, displayAlertView);
}
return YES;
}
// dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
参数一:确认队列;参数二:提交上下文,也就是参数;参数三:调用的C语言方法都是dispatch_function_t类型
我们可以通过NSThread类查看当前队列和主队列:
NSLog(@"current thread = %@",[NSThread currentThread]);
NSLog(@"main thread = %@",[NSThread mainThread]);
解决方案:
使用dispatch_sync方法
讨论:
任何与UI无关的任务,都可以使用全局并发队列(globle concurrent queues),它既可以使用同步方法,也允许使用异步方法。
对于同步方法来说,它并不意味着你的应用要等着任务完成后才能继续,而是说在那个并发线程上的其他任务,需要等待这个队列上 的当前任务完成后才能继续执行。你的程序将会一直执行,因为并发队列并不执行在主队列上。(只有一个例外,当一个任务通过dispatch_sync提交到并发队列concurrent queue或者串行队列serial queue,如果可能的话,iOS将会让这个任务执行在当前队列上,而这个current queue也可能是主线程,它取决于那个时候代码路径在哪里。这个是GCD在编码时候的一个优化。)
如果你提交一个同步任务到并发队列,同时提交另一个同步任务到另一个并发队列,这两个同步任务对对方来说都是异步执行的,因为它们是跑在不同的并发队列上的。
理解以上概念是很重要的。如果你想要确保任务B必须等任务A结束后才开始,你可以将它们同步的放到同一个队列上去。
你可以使用dispatch_sync方法执行同步任务在一个调度队列上,你所需要做的只是,提供一个负责处理任务的队列,以及一个block代码来执行在这个队列上。
下面这个例子,它打印了1-1000两次,一个队列完成后接着另一个队列,并没有阻塞主线程。我们可以创建一个block对象,来为我们做计算,并且同步地调用相同的block对象两次
void (^printFrom1To1000)(void) = ^{
NSUInteger counter = 0;
for(counter = 1 ; counter <= 1000 ; counter++){
NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
}
};
//-------------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(concurrentQueue, printFrom1To1000);
dispatch_sync(concurrentQueue, printFrom1To1000);
运行的结果是,两个队列依次执行,把第一个执行完后,再打印第二个1-1000,两个队列都执行在主线程上,即使你是使用并发队列来执行这个任务的。原来,这是GCD的优化。dispatch_sync方法将会使用当前线程(就是你用来调度任务的线程),在任何可能的时候,作为已经被编入GCD的优化的一部分。苹果官方是这样说的:
As an optimization, this function invokes the block on the current thread when possible.
C语言的调用方法是这样子的:
void printFrom1To1000 (void *paramContext){
NSUInteger counter = 0;
for(counter = 1 ; counter <= 1000 ; counter++){
NSLog(@"counter = %lu - Thread = %@",(unsigned long)counter,[NSThread currentThread]);
}
};
//-------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
dispatch_sync_f(concurrentQueue, NULL, printFrom1To1000);
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
第一个参数是指定队列的优先级,可选项分别有:DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW
级别越高,任务就会被分配更多的CPU时间片段
第二个参数是保留值,你只需要记住永远给它赋值0就对了
这是GCD最精华的部分,可以同时在main、serial、concurrent上异步执行block代码。
解决方案:
1. dispatch_async
2. dispatch_async_f
场景:现在想要从网络上下载一个图片,下载完成后显示给用户。
思路:
思路:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block UIImage *image = nil;
dispatch_sync(concurrentQueue, ^{
/*Down load the image here*/
});
dispatch_sync(dispatch_get_main_queue(), ^{
/*Show the image to the user here on the main queque*/
});
});
再具体一点
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block UIImage *image = nil;
dispatch_sync(concurrentQueue, ^{
NSString *urlAsString = @"http://image2.sina.com.cn/ent"\
"/2004-12-18/1103339247_UQaqtq.jpg";
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSError *downloadError = nil;
NSData *imageData = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:nil
error:&downloadError];
if (!downloadError && imageData) {
imageData = [UIImage imageWithData:imageData];
}else if(downloadError){
NSLog(@"Error happened = %@",downloadError);
}else{
NSLog(@"No data could get downloaded from the URL");
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
/* Show the image */
}else{
NSLog(@"Image isn't downloaded. Nothing to display");
}
});
});
解决方案:
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#^(void)block#>)
参数:纳秒单位的延迟时间,调度的队列,Block对象;
dispatch_after_f(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
参数:纳秒单位的延迟时间,调度的队列,给C方法使用的上下文,C方法
注意:虽然延迟的单位是纳秒,但是它还是由iOS来觉得派遣延迟的粒度大小,因此,延迟的时间也许不会像设定的那么精确。
代码示例:
double delayInSeconds = 2.0;
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(delayInNanoSeconds, concurrentQueue, ^{
/* Perform your operations here */
});
dispatch_time_t类型是一个绝对时间的抽象呈现。为了得到这个值,你可以使用上面代码片段中的dispatch_time方法。以下参数是你可以传递给dispatch_time方法的:
(Base time,Delta to add to base time)
base time可以设定为DISPATCH_TIME_NOW。无论最后B或者D设置为什么值,最终dispatch_time方法的时间总和就是B+D。
B表示从某一时刻开始的时间,D就是从B开始要延迟的那个时间。
举例来说:
从现在开始的3s后
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, 3.f * NSEC_PER_SEC);
或者从现在开始的半秒后
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, (1/2.f) * NSEC_PER_SEC);
C语言的实现方式:略,不想写了
解决方法:
dispatch_once
dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)
参数:
Token:dispatch_once_t类型的token可以保留第一次从GCD中产生的token;
block 对象;
代码:
static dispatch_once_t onceToken;
void (^executedOnlyOnce)(void) = ^{
static NSUInteger numberOfEntries = 0;
numberOfEntries ++;
NSLog(@"Excuted %lu time(s)",(unsigned long)numberOfEntries);
};
//--------------------------------
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_once(&onceToken, ^{
dispatch_async(concurrentQueue, executedOnlyOnce);
});
dispatch_once(&onceToken, ^{
dispatch_async(concurrentQueue, executedOnlyOnce);
});
执行的结果是:
Excuted 1 time(s)
实例:使用dispatch_once制作singleton。
-(id)sharedInstance{
static MySingleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [MySingleton new];
});
return sharedInstance;
}