一. GCD 是Grand Central Dispatch 的缩写
GCD是Apple开发的一个多核编程的解决方法。GCD是一个代替NSThread,NSOperator技术的高效和强大的技术。
GCD 和block 的配合使用,可以方便的进行多线程编程。
二.应用举例
让我们啦看一个编程场景。我们在iPhone上做一个下载的功能,该功能非常简单,就是在iPhone上放置一个按钮,当我们点击按钮的时候开始惊进行下载,下载完成之后,件内容显示在控件上面。
三.不用GCD前
虽然功能简单,但是我们必须把下载过程放到后台线程中,否则会阻塞UI线程显示。所以,如果不用GCD,我们需要些如下3个方法:
(1). someClick 方法是点击按钮后的代码,我么可以看到我们用NSInvocationOperation 建立一个后台线程,并且放到NSOperationQueue 中。后台线程执行downLoad方法。
(2).download方法处理下载网页的逻辑。下载完成后调用performSelectorOnMainThread执行download_completed 方法。
(3).download_completed 进行clear up的工作,并把下载的内容显示到文本控件上来。
这3个方法的代码如下。可以看到,虽然开始下载——》下载中——》下载完后这3个步骤是整个功能的三步。但是他们却被切分成3块,他们之间是3个方法,所以还需要传递数据参数,另外,下载可能放到Model的类中来做,而界面的控制放到ViewController层来做,这使得本来就分开的代码变得更加散落。代码的可读性大大降低。
static NSOperationQueue *queue;
-(IBAction)someClick:(id)sender
{
self.indicator.hidden=no;
[self.indicator startAnimating];
queue=[[NSOperatorQueue alloc]]init];
NSInvocationOperation *op=[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downLoad) object:nil] autorelease];
[queue addOperation:op];
}
-(void)downLoad
{
NSURL *url=[NSURL URLWithString:@"http://www.youdao.com"];
NSError *error;
NSString *data=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if(data!=nil){
[self performSelectorOnMainThread:@selector(downLoad_complement:) withObject:data waitUntilDone:NO];
}else{
NSLog(@"error when download:%@", error);
}
}
-(void)downLoad_complement:(NSString *)data
{
NSLog(@"call back");
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.content.text = data;
NSLog(@"==data=%@",data);
}
使用GCD后,以上的3个方法都可以放到一起,如下所示
//代码块1
self.indicator.hidden=no;
[self.indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//代码块2
NSURL *url=[NSURL URLWithString:@"http://www.youdao.com"];
NSError *error;
NSString *data=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if(data!=nil){
//代码会3
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.content.text=data;
NSLog(@"===data==%@",data);
});
}else{
NSLog(@"error when download:%@", error);
}
});
首先我们可以看到,代码变短了。因为少了原来 3 个方法的定义,也少了相互之间需要传递的变量的封装。
另外,代码变清楚了,虽然是异步的代码,但是它们被 GCD 合理的整合在一起,逻辑非常清晰。如果应用上 MVC 模式,我们也可以将 View Controller 层的回调函数用 GCD 的方式传递给 Modal 层,这相比以前用 @selector 的方式,代码的逻辑关系会更加清楚。
block的定义
dispatch_async(dispatch_get_global_queue(0,0),^{
// someThing
});
从上面大家可以看出,block有如下特点:
1.程序块可以再代码中以内联的方式定义。
2.程序块可以访问在创建他的范围内的可用变量。
系统提供的 dispatch 方法
为了方便地使用 GCD,苹果提供了一些方法方便我们将 block 放在主线程 或 后台线程执行,或者延后执行。使用的例子如下:
后台执行
dispatch_async(dispatch_get_global_queue(0,0),^{
//something
});
//主线程执行:
dispatch_async(dispatch_get_main_queue(), ^{
//something
});
//一次性执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//code to be executed once
});
//延迟2秒执行:
double delayInSeconds=2.0;
dispatch_time_t popTime=dispatch_time(DISPATCH_TIME_NOW, delayInSeconds);
dispatch_after(popTime,dispatch_get_main_queue(), ^{
//code to be executed on the main queue after delay
});
// dispatch_queue_t 也可以自己定义,如要自定义queue,可以用dispatch_queue_create 方法,示例如下:
dispatch_queue_t urls_queue=dispatch_queue_create("blog.devtang.com",NULL);
dispatch_async(urls_queue, ^{
//your code
});
dispatch_group_t group=dispatch_group_create();
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//并行执行的线程一
});
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//并行执行的线程二
});
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
//汇总结果
});
// 修改 block 之外的变量
// 默认情况下,在程序块中访问的外部变量是复制过去的,即写操作不对原变量生效。但是你可以加上 __block 来让其写操作生效,示例代码如下:
__block int a=0;
void (^foo)(void)=^{
a=1;
};
foo();
// 这里,a的值被修改为1
//使用 block 的另一个用处是可以让程序在后台较长久的运行。在以前,当 app 被按 home 键退出后,app 仅有最多 5 秒钟的时候做一些保存或清理资源的工作。但是应用可以调用 UIApplication 的beginBackgroundTaskWithExpirationHandler方法,让 app 最多有 10 分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
// 让程序在后台长久运行的示例代码如下
在APPDelegate.h 文件中@property (nonatomic,assign) UIBackgroundTaskIdentifier backgroundUpdateTask;
AppDelegate.m 文件中
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
//在这里加上你需要长久运行的代码
[self endBackgroundUpdateTask];
}
- (void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
大家尽量的使用GCD 蜡处理后台线程和UI线程的交互。