Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务
这里不讲一些概念性的东西,也不讲同步任务sync,只说使用异步任务async的情况
经常使用的场景是 :
- 开启一个子线程,做一些耗时操作然后回到主线程刷新UI。比如网络请求到数据,需要for循环解析构造数据,然后tableview 刷新。
- 多个子线程同时执行,全部执行成功后,返回到主线程。如一个页面有三个请求,三个请求完成后刷新tableview。如果不特殊处理需要每个网络请求完成后,刷新一次tableview,也就是一共刷新三次。但是这样做显然是不合理的(特需需求除外)
- 延迟执行某任务
- 单例
- 多网络请求,上个网络完成后才能执行下一个网络请求,也就是说用异步任务去做同步的事情 (GCD信号量) 。比如说,三个网络,有两个网络执行完才能执行第三个。
在使用前,需要了解 -- 队列、任务
任务就是我们线程回调Block里边需要执行的代码,可以同步sync执行,也可以异步async执行
队列就是你这个任务在什么“地方”执行,分为串行队列Serial Dispatch Queue 和并发队列Concurrent Dispatch Queue。
苹果给我们开发者提供了两种默认队列,主队列dispatch_get_main_queue 与 全局队列 dispatch_get_global_queue。如果不想使用系统提供的,我们也可
以自定义队列:
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create方法的第一个参数是你队列的名称,注意是没有@的,第二个参数是用来声明这个队列是串行队列还是并发队列
DISPATCH_QUEUE_CONCURRENT //并发类型
DISPATCH_QUEUE_SERIAL //串行队列
接下来看每个场景对应的使用代码示例(序号与上边应用场景一一对应)
- 耗时操作后返回主线程
[manager startRequestWithSuccess:^(SQApiResponse *response) {
NSLog(@"openAPI快递信息接口查询结果:%@",response.responseObject);
/*
开启子线程
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
也可以是自定的,不一定要用全局的
例:dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i<1000; i++) {
MyObjc *obj = [[MyObjc alloc]init];
obj.name = @"测试";
[arr addObject:obj];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
} failure:^(SQApiResponseError *error) {
NSLog(@"openAPI接口错误:%@",error.errorDic);
}];
- 多个子线程同时执行,采用线程组方式或信号量方式(这里只例举线程组dispatch_group,信号量请看第5条)
//声明线程组
@property (nonatomic, strong) dispatch_group_t dispatchGroup;
//========================我是分割线========================//
- (void)viewDidLoad {
//将每一个网络都加入线程组
dispatch_group_enter(self.dispatchGroup);
//网络请求1
[self getBindCommunityArr];
dispatch_group_enter(self.dispatchGroup);
//网络请求2
[self getBindCommunityArr];
dispatch_group_enter(self.dispatchGroup);
//网络请求3
[self getBindCommunityArr];
//3个网络全部请求完成后,dispatch_group_notify将自动回调
dispatch_group_notify(self.dispatchGroup, dispatch_get_main_queue(), ^{
//数据处理,然后刷新tableview
[self.tableView reloadData];
});
}
/**
获取用户绑定小区列表
*/
-(void)getBindCommunityArr{
SQApiRequest *request = [SQApiRequestFactory getUserBindCommunityInfo];
[request startRequestWithSuccess:^(SQApiResponse *response) {
//请求成功后,处理数据,然后调用dispatch_group_leave,移除线程组
dispatch_group_leave(self.dispatchGroup);
} failure:^(NSError *error) {
dispatch_group_leave(self.dispatchGroup);
}];
}
- 延迟执行某操作
//API
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,dispatch_block_t block);
//使用
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf getServiceDetailInfo];
});
//说明:第一个参数是什么时间开始,默认DISPATCH_TIME_NOW标识现在开始,第二个参数是多久后(几秒)执行Block,第三个参数是哪个队列执行,第四个是block任务代码块。
- 单例,这个就比较简单,几乎都在用。但是不仅仅是单例使用,也可以用在别处,使用此API后,若APP未被杀掉进程,代码将不会执行第二次
static GigApiRequestFactory *g_gigApiRequestFactory = nil;
@implementation GigApiRequestFactory
+ (GigApiRequestFactory*)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
g_gigApiRequestFactory = [[GigApiRequestFactory alloc] init];
});
return g_gigApiRequestFactory;
}
- 信号量进行网络拦截并且顺序执行
对于dispatch_semaphore_create的用法完全可以根据自己代码习惯处理业务逻辑,有的人喜欢先dispatch_semaphore_wait,有的人喜欢先dispatch_semaphore_signal,但是无论怎么,他们都是成对出现的,也就是一个异步任务对应一个dispatch_semaphore_signal和一个dispatch_semaphore_wait。
dispatch_semaphore_create的参数表示的是此时可以进行的任务访问量,我理解为当前的信号量数值,dispatch_semaphore_wait会对信号量-1,dispatch_semaphore_signal会+1,如果当前信号量>=0时,就可以继续进行访问执行下一步操作,否则等待。
比如说,我现在有三个网络,我设置了允许最大访问量是1,那么此时信号量是1,然后dispatch_semaphore_wait后信号量-1为0,继续执行下一个网络,再次dispatch_semaphore_wait后信号量-1为-1,这是信号量小于0,等待,不执行下一个网络,等其中一个网络回调后,调用dispatch_semaphore_signal后信号量+1为0,满足条件,继续执行下一个网络,以此循环,直到全部结束
@property(nonatomic,strong)dispatch_semaphore_t semaphore; //全局信号量
-(void)viewDidAppear:(BOOL)animated{
//创建信号量:dispatch_semaphore_create方法的参数代表允许多少个网络同时进行,可以为零
self.semaphore = dispatch_semaphore_create(1);
for (int i = 0; i<10; i++) {
[self getNewsListDataWithLongitude:30 latitude:30 loadMore:NO];
}
}
-(void)getNewsListDataWithLongitude:(CGFloat)longitude latitude:(CGFloat)latitude loadMore:(BOOL)loadMore{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[[NewsFeedsSDK sharedInstance]loadNewsWithChannel:self.channelInfo pageSize:5 loadType:loadMore?0:1 longitude:longitudeNew latitude:latitudeNew block:^(NFNews *newsList, NSError *error) {
if (!error) {
@autoreleasepool{
dispatch_semaphore_signal(self.semaphore);
}
}else{
[self.tableView.header endRefreshing];
[self.tableView.footer endRefreshing];
dispatch_semaphore_signal(self.semaphore);
}
}];
}
说明:
- 因为代码均为项目中代码,涉及很多业务逻辑,所以将不必要的全部删除了,删除期间可能引起语法错误。
- 我在使用 dispatch_async+大量for循环的时候,基本都会配合@autoreleasepool配合使用,以优化性能,防止内存问题
网友评论