iOS 关于dispatch_semaphore_t、dispatch_source_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知

问题来源: 最近遇到了一个多网络异步回调的问题,其实也就是我们请求的数据是异步的,我们使用了带有返回值的方法,结果我们先获取的结果都是空的,这个其实对新手来说,可能不知道为什么会有这个结果,这个其实稍微百度一下就能找到答案,不过还是写一下,为大家处理一下盲区


我们主要介绍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 = raybon
2016-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,也可以在源执行中调用这个block
dispatch_source_set_registration_handler(source, ^{
    NSLog(@"我是注册收到的回调");
});

```
* 这里我们看一下API的作用
   该API主要是监听数据更新完毕的block回调,最简单的方法就是网络请求完毕需要更新UI的时候,我们会在这里面执行block
    ```      
void

dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t handler);
* 合并数据源的API
dispatch_source_merge_data(source, 1);
```
该API主要提供数据更新完毕时候调用一次,第二个参数不能设置为0, 否则block无反应
* 其他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 的结束,释放信号,这个暂且未研究完毕,完毕研究会继续追加此文。也希望有使用到这个的大神,直接留言,会在此文追加。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • Dispatch Sources 现代系统通常提供异步接口,允许应用向系统提交请求,然后在系统处理请求时应用可以继...
    好雨知时节浩宇阅读 3,813评论 2 5
  • 支持原创 现代系统通常提供异步接口,允许应用向系统提交请求,然后在系统处理请求时应用可以继续处理自己的事情。Gra...
    John_LS阅读 3,570评论 3 2
  • Dispatch Sources 现代系统通常提供异步接口,允许应用向系统提交请求,然后在系统处理请求时应用可以继...
    YangPu阅读 303评论 0 0
  • 程序中同步和异步是什么意思?有什么区别? 解释一:异步调用是通过使用单独的线程执行的。原始线程启动异步调用,异步调...
    风继续吹0阅读 1,027评论 1 2
  • 给大地来点汤料 用 二月衍接 时不时的不顾人情 用猛烈 砸湿 檐下的燕窝 只有,地皮下的草芽 最为饥渴 被他...
    一池凹水凸龙阅读 222评论 0 4