iOS 关于dispatch_semaphore_t、dispatch_source_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知
来源:互联网 发布:爱上一个中国女人知乎 编辑:程序博客网 时间:2024/06/08 19:33
问题来源: 最近遇到了一个多网络异步回调的问题,其实也就是我们请求的数据是异步的,我们使用了带有返回值的方法,结果我们先获取的结果都是空的,这个其实对新手来说,可能不知道为什么会有这个结果,这个其实稍微百度一下就能找到答案,不过还是写一下,为大家处理一下盲区
我们主要介绍3中方法,来获取异步方法中的回调结果
一、 使用信号量 dispatch_semaphore_t 控制请求
- 我们先看一下实际应用中的一些列子,我们尽量仿真模拟真实的网络请求我们看下代码如何工作的:
/*! * @author Raybon.Lee, 16-04-07 10:04:02 * * @brief 从服务器请求数据,异步返回数据,并更新UI,我们这里尽量高仿真实的网络请求环境 * * @return 返回一个数组 * * @since <#1.0#> */- (__kindof NSArray *)fetchDataFromServe{ //假如下面这个数组是用来存放数据的 NSMutableArray * array = [NSMutableArray arrayWithCapacity:0]; //下面这个来代替我们平时常用的异步网络请求 dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i=0; i<10; i++) { [array addObject:[NSNumber numberWithInt:i]]; } NSLog(@"array = %@",array); }); return array;}
正常情况下,我们可能会有这种写法,因为没注意到返回数据是异步的
我们看下调用返回:NSArray * resultArray = [self fetchDataFromServe]; NSLog(@"resultArrsy = %@",resultArray);
*控制台输出结果:
2016-04-07 10:30:45.868 ABNumDemo[4129:1394388] resultArrsy = ()2016-04-07 10:30:45.875 ABNumDemo[4129:1394441] array = ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
我们第一眼看到的感觉一般都是很明确,这个值肯定存在,那为什么最终获取的值就是不对呢,有时候考虑问题很容易凌乱,上面的结果一定反馈给我们信息了,结果是空的
- 现在我们修改一下方法,使用信号量控制,顺便说一下信号量是如何工作
- (__kindof NSArray *)fetchDataFromServe{ //修改下面的代码,使用信号量来进行一个同步数据 //我们传入一个参数0 ,表示没有资源,非0 表示是有资源,这一点需要搞清楚 //补充:这里的整形参数如果是非0 就是总资源 dispatch_semaphore_t semaoh = dispatch_semaphore_create(0); //假如下面这个数组是用来存放数据的 NSMutableArray * array = [NSMutableArray arrayWithCapacity:0]; //下面这个来代替我们平时常用的异步网络请求 dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i=0; i<10; i++) { [array addObject:[NSNumber numberWithInt:i]]; } NSLog(@"array = %@",array); //谢谢叶神的纠正 修正:发送信号,信号量 管理资源数+1 车辆如果遇到绿色信号灯,等待的车辆就会减少,也就是资源数减少,一直减少到 dispatch_semaphore_wait 这个函数返回 0 才会继续执行,//之前注释有点问题,这个通过相当于 放行操作 执行信号数量+1,这个地方一定触发信号,就会通知wait 函数进行 -1 操作,也就是后面可以继续通行 dispatch_semaphore_signal(semaoh); }); //信号等待 时,资源数 -1 阻塞当前线程 这个难理解的可以理解成等待红绿灯的车辆,红灯等待车辆 //车辆自然是累加的排队等候,没有资源,会一直触发信号控制 dispatch_semaphore_wait(semaoh, DISPATCH_TIME_FOREVER); return array;}
- 信号量的理解:
我们初始化的时候会先设置一个信号总量,如果信号总量的整形参数是0 ,那么就是没有资源需要等待,我们如果下面执行wait 操作,那么相当于线程拥堵,执行信号-1 操作,如果现在是绿灯通行状态,我们会设置 sigle 信号,执行一个信号+1 操作,告诉当前线程,有一个信号可以释放了,大概可以这么来理解
修改之后我们再来看一下控制台的输出:是不是看到我们想要的效果了,其实我们就是要异步回调有结果之后再释放资源数2016-04-07 10:36:17.361 ABNumDemo[4136:1395451] array = ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)2016-04-07 10:36:17.364 ABNumDemo[4136:1395425] resultArrsy = ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
二、 下面我们再看下
dispatch_group_t
的使用方法
- 同样我们还是写一个仿真请求网络的地方
- (NSArray *)fetchTheNetUserData{ dispatch_group_t group = dispatch_group_create(); NSMutableArray * array = [NSMutableArray array]; for (int i=0; i<5; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [array addObject:[NSNumber numberWithInt:i]]; }); } return array;}
我们现在调用一下函数看下输出:
NSArray * groupArray = [self fetchTheNetUserData]; NSLog(@"grouparray = %@",groupArray);2016-04-07 11:17:53.508 ABNumDemo[4143:1400362] grouparray = ()
这个结果其实不是想要的结果,我们稍微修改一下
- (NSArray *)fetchTheNetUserData{ //使用GCD创建一个group 组,这个其实不是很好理解,我们可以理解成,创建一个组,然后给这个组添加任务,等到所有组的任务都结束之后在进行一个颁奖典礼,这个就是我们要的效果 dispatch_group_t group = dispatch_group_create(); NSMutableArray * array = [NSMutableArray array]; for (int i=0; i<5; i++) { //group 进入一个组 和dispatch_group_leave 这两个必须是成对出现,缺一不可,如果对应不上则会出现线程阻塞, dispatch_group_enter(group); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [array addObject:[NSNumber numberWithInt:i]]; //dispatch_group_leave 离开一个组 ,这个和排毒其实有点类似 dispatch_group_leave(group); }); } //dispatch_group_wait 这个函数返回 0则会继续执行,否则一直等待 group 组内的所有成员任务完毕,这个其实也是资源数增加的一个函数,等待当前线程结束 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"array = %@",array); return array;}
- 增加 ,一下两种形式等价,只不过使用方式不同,看自己需求
这两个还是稍微有点差别,一个是设置资源线程等待 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);下面这个是通知形式的,可以做一些没有返回值方法的更新UI操作 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //[tableView reloadData]; });
修改之后的输出环境:
2016-04-07 11:22:25.251 ABNumDemo[4149:1401314] array = ( 0, 1, 2, 3, 4)2016-04-07 11:22:25.258 ABNumDemo[4149:1401314] grouparray = ( 0, 1, 2, 3, 4)
通过以上两种方法,或许我们已经看到想要的结果。
- 有个需要注意的地方,在我们使用以上两者中的时候,如果使用了AF嵌套,AF缺省的时候是主线程,而我们的wait 函数也是主线程,此时会造成死锁,平时使用的时候要注意这一点。这两天在调试的时候发现了这个,表明一下
三、 使用block 获取异步回调的结果
- 我们还是看代码,直观明了:
- (void)queryTheDataRequestWithCompletion:(void (^)(NSString * string))block{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ block(@"test-block"); });}- (void)requestDataFormServe:(CallBlock)block{ block(@{@"name":@"raybon"});}
调用这两个方法:
[self requestDataFormServe:^(NSDictionary *dict) { NSString * name = dict[@"name"]; NSLog(@"name = %@",name); }]; [self queryTheDataRequestWithCompletion:^(NSString *string) { NSLog(@"string = ^%@",string); }];
接着我们看下输出控制台的结果:
2016-04-07 11:33:25.153 ABNumDemo[4155:1402837] name = raybon2016-04-07 11:33:25.160 ABNumDemo[4155:1402877] string = ^test-block
这种采用block的方法,我觉得是不是更好理解呢,还好用,但是这个要针对不同的情况采取不同的方法,并不是固定的,这里只是提供几种思路而已,有更好的想法都可以留言增加上去,技术么,当然是越优化越好。
补充一、dispatch_semaphore_t 控制请求
增加一个通过源控制的请求,也是一个线程安全的,这个用的还是GCD的API,我们用代码分析
//创建一个源 DISPATCH_SOURCE_TYPE_DATA_ADD 源类型是增加数据dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0)); //设置事件回调dispatch_source_set_event_handler(source, ^{ //数据执行完毕,通知我更新UI操作 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"## 我已经收到数据,开始更新UI操作 ##"); dispatch_source_cancel(source); });}); //source 默认处于suspend 暂停 状态 ,我们要执行 dispatch_resume 继续执行dispatch_resume(source);dispatch_async(dispatch_get_main_queue(), ^{ //此处是网络请求,请求到数据之后,我们更新一个源 NSLog(@"## 通知数据合并更新 ##"); dispatch_source_merge_data(source, 1);});unsigned long num = dispatch_source_get_data(source);NSLog(@"data = %ld",num); //得到dispatch 源 ,获取的是dispatch_source_create 的第三个参数,unsigned long num1 = dispatch_source_get_mask(source); //获取dispatch 源 获取的是dispatch_create 的第二个参数位置的信息unsigned long num2 = dispatch_source_get_handle(source); // 源取消时的回调,用于关闭流处理dispatch_source_set_cancel_handler(source, ^{ NSLog(@"## 如果source 被取消,则执行这个函数 ##");}); //检测源是否取消,如果是非0 则表示取消long cancelNum = dispatch_source_testcancel(source); //可用于源启动时调用block,调用完成后释放这个block,也可以在源执行中调用这个blockdispatch_source_set_registration_handler(source, ^{ NSLog(@"我是注册收到的回调");});
- 这里我们看一下API的作用
该API主要是监听数据更新完毕的block回调,最简单的方法就是网络请求完毕需要更新UI的时候,我们会在这里面执行blockvoiddispatch_source_set_event_handler(dispatch_source_t source,dispatch_block_t handler);
- 合并数据源的API
该API主要提供数据更新完毕时候调用一次,第二个参数不能设置为0, 否则block无反应dispatch_source_merge_data(source, 1);
- 其他API就是取消源的API,我们不用的时候可以取消掉。
总结:
以上几种方法适合我们在做一些简单的遍历操作的时候挺适合的,针对请求的一些异步数据,还是提醒一点,使用这个一定要注意是否有嵌套主线程,造成线程阻塞,只要思路清晰,处理这些问题还是没问题的
- 最后我们扩充一个资源共享的问题,同样我们还是使用信号量控制
这个是线程安全的,每次只允许一个线程进行读写操作,遇到读写问题可以参考这里哦
//这里我们指定一个资源 wait 返回值就不会为0 执行发出信号操作 dispatch_semaphore_t semat = dispatch_semaphore_create(1); NSMutableArray * array = [NSMutableArray array]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i=0; i<1000; i++) { dispatch_semaphore_wait(semat, DISPATCH_TIME_FOREVER); [array addObject:[NSNumber numberWithInt:i]]; dispatch_semaphore_signal(semat); } });
- 补充:
使用信号量控制AF请求,会遇到阻塞,我们这个时候除了使用group 信号控制,这里推荐叶神提供的 runloopObserve ,简单理解就是通过runloop 的进入,到runloop 的结束,释放信号,这个暂且未研究完毕,完毕研究会继续追加此文。也希望有使用到这个的大神,直接留言,会在此文追加。
作者:Raybon_lee
链接:http://www.jianshu.com/p/a4ea43179870
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
阅读全文
0 0
- iOS 关于dispatch_semaphore_t、dispatch_source_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知
- iOS dispatch_source_t
- iOS dispatch_group_t
- iOS dispatch_semaphore_t
- 关于iOS通知的简单认识
- 关于linux异步通知signal 和QT的信号槽
- 关于IOS中 MVC模式和 回调通知的问题
- iOS CGD dispatch_group_t 等待
- Android的Notification通知的简单实用
- iOS关于本地推送通知的简单用法
- NSNotificationCenter defaultCenter 通知的简单实用
- iOS多线程中的dispatch_semaphore_t
- iOS多线程中的dispatch_semaphore_t semaphore(dispatch组和信号量。)
- IOS 通知+异步通知详解
- 关于支付宝异步通知的状态
- 高精度的定时器:dispatch_source_t
- iOS通知的简单使用
- dispatch_source_t
- Linux中静态库和共享库的区别
- ASP一句话木马(转)
- tomcat配置字符集防止ajax提交乱码
- Artifact Project3:war exploded: Error during artifact deployment. See server log for details.
- TypeScript介绍
- iOS 关于dispatch_semaphore_t、dispatch_source_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知
- 资料汇总-学习笔记1
- Spring boot 整合CXF开发web service
- java.util.concurrent 之 Condition
- android触摸事件分发与处理简述
- JAVA_方法_输入两个数打印他们的和差积商
- iPhone X home指示键适配
- java 调用 本地命令Ping 实例
- android App使用360加固,walle多渠道打包