GCD
2017-09-25 本文已影响110人
醉看红尘这场梦
![](https://img.haomeiwen.com/i1754828/028458b63192d0c5.png)
GCD
场景
我们要在iPhone上做一个下载网页的功能,该功能非常简单,就是在iPhone上放置一个按钮,单击按钮的时候,显示一个转动的圆圈,表示正在进行下载,下载完成之后,将内容加载到界面上的一个文本控件中
使用GCD前
- 虽然功能简单,但是我们必须把下载过程放到后台线程中,否则会阻塞UI线程显示,所以如果不用GCD,我们需要三个方法
- someClick 方法是单击按钮后的代码,可以看到我们用NSInvocationOperation建了一个后台线程,并且放到NSOperationQueue中。后台线程执行download方法
- download方法处理下载网页的逻辑,下载完成后用performSelectorOnMainThread执行download_completed方法
- download_completed执行clear up的工作,并把下载的内容显示到文本空间中。
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *indicator;
@property (strong, nonatomic) IBOutlet UILabel *context;
@end
@implementation ViewController
static NSOperationQueue *queue;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)someClick:(id)sender {
self.indicator.hidden = NO;
[self.indicator startAnimating];
queue = [[NSOperationQueue 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_completed:) withObject:data waitUntilDone:NO];
}else{
NSLog(@"error when download:%@",error);
[queue release];
}
}
-(void)download_completed:(NSString *)data{
NSLog(@"call back");
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.context.text = data;
[queue release];
}
使用GCD后
- 如果使用GCD,以上的三个方法放到一起
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *indicator;
@property (strong, nonatomic) IBOutlet UILabel *context;
@end
@implementation ViewController
static NSOperationQueue *queue;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)someClick:(id)sender {
self.indicator.hidden = NO;
[self.indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
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_completed:) withObject:data waitUntilDone:NO];
}else{
NSLog(@"error when download:%@",error);
[queue release];
}
});
}
-(void)download_completed:(NSString *)data{
NSLog(@"call back");
[self.indicator stopAnimating];
self.indicator.hidden = YES;
self.context.text = data;
[queue release];
}
- 首先我们可以看到,代码变短了。因为少了原来三个定义的方法,也少了相互之间需要传递的变量的封装
- 另外,代码变清楚了,虽然是异步的代码,但是它们被GCD合理地整合在一起,逻辑非常清晰,如果应用MVC模式,我们可以将View Controller 层的回调函数用GCD的方式出传递给Model层,这相比以前用的@selector的方式,代码的逻辑关系更加清楚
blcok的定义
- 简单block的定义有点像函数指针,差别是用 ^ 替代了函数指针的*符号
//声明变量
(void)(^loggerBlock)(void);
//定义
loggerBlock = ^{
NSLog(@"Hello workd");
};
//调用
loggerBlcok();
- 但是大多数情况下,我们使用内联的方式来定义它,即将它的程序块写在调用的函数里面
//列如这样
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//something
});
- 从上面大家可以看出,block有如下特点:
- 程序块可以在代码中内联的方式定义。
- 程序块可以访问在创建它的范围内的可用的变量。
系统提供的dispatch方法
- 为了方便地使用GCD,苹果提供了一些方法方便我们将blcok放在主线程或后台线程执行,或者延后执行,使用例子如下所示:
//后台执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
//主线程执行
dispatch_async(dispatch_get_main_queue(), ^{
});
//一次性执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
//延迟2秒执行
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
});
- dispatch_queue_t也可以自己定义,如要自定义queue,可以用dispatch_queue_create方法
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
});
dispatch_release(urls_queue);
- 另外,GCD还有一些高级用法,列如让后台两个线程并执行,然后等两个线程都结束后,再汇报总执行结果,这个可以用dispatch_group,dispatch_group_async和dispatch_group_notify
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
//并执行的线程一
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
//并执行的线程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
//汇总结果
});
修改block之外的变量
- 默认情况下,在程序块中访问的外部变量是复制过去的,即写操作不对原变量生效,但是你可以加上__block来让其写操作生效
__block int a = 0;
void (^foo)(void) = ^{
a = 1;
};
foo();
//这里,a的值被修改为1
后台运行
- 使用block的另一个用处是可以让程序在后台较长久地运行
<!--AppDelegate.h-->
@property(assign,nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
<!--AppDelegate.m-->
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[self beingBakcgroundUpdateTase];
[self endBackgounrdUpdateTask];
}
-(void)beingBakcgroundUpdateTase{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgounrdUpdateTask];
}];
}
-(void)endBackgounrdUpdateTask{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
总结
总体来说,GCD能够极大地方方便开发者进行多线程编程,大家应该尽量使用GCD来处理后台线程和UI线程的交互