用GCD group与信号量实现多网络请求统一处理数据

在最近工作中,由于种种原因,经常碰到这样一个需求:在显示一个界面之前,需要同时从多个接口获取数据,对全部的数据进行综合处理后才能显示界面。感谢后台大佬和产品(👎 ),下面谈谈我实现的思路与方法。(其实是当笔记:-D)

总体思路

界面上效果:进入控制器后,进行网络请求,界面显示未转菊花动画,等数据全部获取并处理后,隐藏菊花,显示正确界面。
问题简化:其实就是一个多线程问题,不管需要同时请求几个接口,这些通过访问接口获取数据的方法都是需要一定时间的。对于这种耗时操作,我们通常需要开辟子线程去处理,在子线程中进行等待,阻塞,主线程只要负责界面显示,在这个例子中是显示菊花,告诉用户我们正在处理数据。等子线程处理完所有数据后,再通知主线程绘制界面。那么最终的问题就是准备如何知道所有的异步网络请求都已经完成,即什么时候数据处理完毕。

技术实现

方法一:

1.使用GCD dispatch_group_async来作为多个异步网络请求任务的“容器”,并在dispatch_group_notify中对返回结果进行处理。

2.使用信号量来控制异步网络请求完成后等待所有数据请求回来统一处理。

demo

        // 创建一个信号量对象
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_group_t group = dispatch_group_create();
        //系统给定义的全局队列,我希望每个请求接口任务都能并发进行,所以选择并行队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //  将请求接口任务添加进group中,并传入block方法,在数据返回并处理完毕以后,执行dispatch_semaphore_signal(semaphore);方法,且是异步操作。
        // 请求红包数据
        dispatch_group_async(group, queue, ^{
            
            [self configCouponData:^{
                dispatch_semaphore_signal(semaphore);
            }];
            
        });
        // 请求商品详情数据
        dispatch_group_async(group, queue, ^{
            
            [self configGoodDetailData:^{
                dispatch_semaphore_signal(semaphore);
            }];
            
        });
        
        // dispatch_group_notify里面的任务是在group里面的所有任务完成后才进行。并且是异步的!!!
        dispatch_group_notify(group, queue, ^{
            
            // 等待信号量资源   如果网络请求未返回,则该线程阻塞
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
           // 此时所有接口的数据都已经返回并处理完毕.
            dispatch_async(dispatch_get_main_queue(), ^{
                // 综合处理所有返回信息并更新UI
                [self finishGetAllData];
            });
            
        });

方法二:

自定义一个变量当做标签,每个接口获取数据后去判断这个标签,当标签显示已经全部数据处理完时,再进行更新。这个方法最主要的是要对标签进行加锁,保障这个标签是线程安全的。我是用dispatch_barrier_async给标签加一个读写锁。

demo

      /// 设置读写锁用的自定义并行队列,注意,必须得是自定义的并行队列才会有效果。
    @property (nonatomic,strong)dispatch_queue_t queue;
     /// 判断是否数据处理完毕的标签
    @property (nonatomic,assign)NSInteger countFlag;

    // 将countFlag的赋值进行dispatch_barrier_async处理
    - (void)setCountFlag:(NSInteger)countFlag{
        dispatch_barrier_async(self.queue, ^{
            _countFlag = countFlag;
         });  
       }

    // 懒加载创建一个自定义并行队列
    - (dispatch_queue_t)queue{
        if (!_queue) {
            _queue = dispatch_queue_create("read_write_lock", DISPATCH_QUEUE_CONCURRENT);
        }  
        return _queue;
    }

    - (void)configData{
        
        // countFlag设置的值为请求接口数+1,每返回一次接口数据,countFlag--,当countFlag == 1时,说明接口已经返回完毕。只所以不以0为标志,是因为如果网络请求回调在界面销毁以后执行,那么self.countFlag == 0,这样就可以不用再进行绘制界面处理。
        self.countFlag = 3;
        
        [self configCouponData:^{

            weakSelf.countFlag --;
            // 判断是否所有接口请求完毕
            if (weakSelf.countFlag == 1) {
                dispatch_async(dispatch_get_main_queue(), ^{
                // 综合处理所有返回信息并更新UI
                [weakSelf finishGetAllData];
                 });
            }
        }]; 
      
       [self configCouponData:^{
          
          weakSelf.countFlag --;
           // 判断是否所有接口请求完毕
          if (weakSelf.countFlag == 1) {
                dispatch_async(dispatch_get_main_queue(), ^{
                // 综合处理所有返回信息并更新UI
                [weakSelf finishGetAllData];
                 });
          }
       }];

    }

    //读的锁

 - (NSInteger)countFlag
  {
      __block NSInteger tempCountFlag;
      dispatch_sync(self.queue, ^{
          tempCountFlag = self.countFlag;
      });
      return tempCountFlag;
  }

具体见利用dispatch_barrier_async设置读写锁

总结:

最终我在项目中用的是方法一,经过几个版本,错误日志中也没发现什么问题。相对来说,我比较喜欢用信号量以及这种代码风格。其实这个方法就是将dispatch_group_notify任务执行的子线程阻塞。是把每个请求数据当做一个资源,这个例子中需要同时2个接口获取数据,就需要2个资源。先创建一个信号量,给0个资源,在dispatch_group_notify任务执行的子线程中会阻塞,等待资源。而每完成一个异步网络请求,就调用一下dispatch_semaphore_signal(semaphore);增加一个资源,等全部网络请求都完成了,进行下一步界面布局的资源就齐全了,dispatch_group_notify中的任务就会继续下去。
甚至还可以给获取全部数据一个最大等待时间,超过了我们就当接口超时处理,只要把
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);设置为不永久等待,判断返回值即可。

ps:

期间,有纠结过dispatch_group_notify里面的任务是否是异步执行的,其实苹果在方法描述中已经告诉我们。
这是关于dispatch_group_t的描述,谷歌翻译下,第一句话就说了队列中加入的块内容是异步执行的。

A group of block objects submitted to a queue for asynchronous invocation.
A dispatch group is a mechanism for monitoring a set of blocks. Your application can monitor the blocks in the group synchronously or asynchronously depending on your needs. By extension, a group can be useful for synchronizing for code that depends on the completion of other tasks.
Note that the blocks in a group may be run on different queues, and each individual block can add more blocks to the group.
The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.

而dispatch_group_notify说明里面的block是加入到group中的,前面说group中的内容是异步执行的,所以dispatch_group_notify里面应该也是异步执行的。

Schedules a block object to be submitted to a queue when a group of previously submitted block objects have completed.

This function schedules a notification block to be submitted to the specified queue when all blocks associated with the dispatch group have completed. If the group is empty (no block objects are associated with the dispatch group), the notification block object is submitted immediately.

When the notification block is submitted, the group is empty. The group can either be released with [dispatch_release](apple-reference-documentation://hccKsBdGNk) or be reused for additional block objects. See [dispatch_group_async](apple-reference-documentation://hcnC1onlYa) for more information.

并且,在oc中,同步操作dispatch_sync的方法是这样的:


dispatch_sync
WechatIMG4.jpeg

而异步操作和dispatch_group_notify是这样的


dispatch_async
dispatch_group_notify

oc在同步操作的block前面,加了noescape,说明该block是非逃逸闭包,即block的作用域不会超过函数本身。
而异步操作和dispatch_group_notify的block前面没有注明,是默认为逃逸闭包,即block的作用域会超过函数本身。
而同步操作,block的作用域不需要超过函数的作用域,它肯定在函数结束前回调结束,所以,会在同步操作的block前加noescape,使编译器对代码优化性能。

而在swift中也是这样的,只不过swift和oc相反,swift3.0以上是默认block回调是非逃逸的,而对于逃逸闭包需要加上关键字@escaping。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容