在实际开发中遇见一个界面多个请求操作的问题

前言

最近项目在接入网络接口时, 有个比较值得注意的地方, 就是一个界面存在多个网络接口, 比如: 在首页界面中存在多个网络请求接口(包括: 图片轮播器, 黄金产品以及黄金样品保单). 如果按照平常那种思维来编码, 很可能会出现UI刷新不出来的问题. 造成界面空白现象. 我先说一下背景: 公司使用的网络类是对AFNetworking框架进行了再度封装, 虽然大家都对它再熟悉不过了, 但是细节上的东西还是需要慢慢品味. 本章文章主要涉及到的是GCD中几个比较常见的函数.重在基础, 大神可以忽略. 如果文章中存在问题, 希望大神在底部留言, 指导一下小白.
思路: 首页中后台提供了三个网络请求接口, 目的就是当所有的请求操作都完成之后, 才去刷新界面, 显示界面. 脑袋中一闪而过的是GCD中的队列组, 将请求操作添加队列组, 最后在dispatch_group_notify中刷新UI.
 eg:  (注意: 在dispatch_group_notify中打印的顺序是随机的)
 
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
 NSLog(@"比如说这里是在子线程上的第一个请求");
 });
 
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
 NSLog(@"比如说这里是子线程上的第二个请求");
 });
 
 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
 NSLog(@"比如说这里是子线程上的第三个请求");
 });
 
 
 // 执行完毕之后的通知
 dispatch_group_notify(group, dispatch_get_main_queue(), ^{
 
 
 NSLog(@"需要在这里回到主线程刷新UI");
 });

  • 我们先看看打印结果, 看看结果中有没有什么猫腻
 打印结果:
 2017-02-15 09:22:08.287 text[987:26314] 比如说这里是子线程上的第二个请求
 2017-02-15 09:22:08.287 text[987:26311] 比如说这里是在子线程上的第一个请求
 2017-02-15 09:22:08.287 text[987:26312] 比如说这里是子线程上的第三个请求
 2017-02-15 09:22:08.302 text[987:26273] 需要在这里回到主线程刷新UI

解释: 单纯这个例子中,很难看出什么端倪, 但是这时候需要想到在开发中使用场景是网络请求. 可能出现的情况: 在实际开发中, 我使用的AFN框架来实现网络请求, AFN中的网络请求都是异步操作, 就是说请求的数据返回后, 才会去刷新相关的UI
如果请求操作有多个, 所以必须要所有的操作都完成之后, 才去刷新UI,这样就可能会造成一个现象, 就是数据是返回了, 但是刷新后UI不显示.最后导致界面空白无物.

  • 解决方法
解决方法: 根据上述的现象, 这里需要引进另一个函数
  • dispatch_semaphore(即: GCD中的信号量), 通过GCD中的信号量实现线程同步
    dispatch_semaphore概念: 信号量是基于计数器的一种多线程同步机制, 主要是用于解决多个线程访问共有的资源时.造成数据紊乱的问题.
    dispatch_semaphore基本原理: 比如说网络请求成功或者失败之后需要将dispatch_semaphore计数器 +1, 请求网络操作完成之后需要将dispatch_semaphore计数器 -1. 如果dispatch_semaphore计数器等于0表示等待.
    加1操作: dispatch_semaphore_signal(semaphore)
    等待操作: dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)

  • 举个例子: 每当有顾客点餐,计数+1,点餐结束-1归零继续等待下一位顾客。比较类似于NSLock(线程锁)。

 eg:
 - (void)request_A {
 
 //创建信号量并设置计数默认为0
 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 
 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
 manager.responseSerializer = [AFHTTPResponseSerializer serializer];
 NSDictionary *parameter = @{@"key":@"value"
 };
 
 [manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {
 
 } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
 
 //计数+1操作
 dispatch_semaphore_signal(sema);
    NSLog(@"在这里获取数据");
 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
 ////计数+1操作
 dispatch_semaphore_signal(sema);
     NSLog(@"在这里获取error");
 }];
 
 //若计数为0则一直等待
 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
 }

为了便于记住, 一下是简写

 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 [网络请求:{
 成功:dispatch_semaphore_signal(sema);
 失败:dispatch_semaphore_signal(sema);
 }];
 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
 
 // 强行解释一波
 通过使用GCD中的信号量可以解决多个操作共用同一资源时, 造成主线程阻塞的问题.
知识扩展
  • 扩展1: GCD中提供了函数, 可以指定操作的执行顺序
> 扩展: 如果我们要指定网络操作的执行顺序的话, 直接使用GCD中的队列, 然后添加依赖即可.
 eg:
 
 //1.任务一:
 NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任务一:")
 }];
 
 //2.任务二:
 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任务二:")
 }];
 
 //3.任务三:
 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" //1.任务三:")
 }];
 
 //4.设置依赖
 [operation2 addDependency:operation1];      //任务二依赖任务一
 [operation3 addDependency:operation2];      //任务三依赖任务二
 
 //5.创建队列并加入任务
 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

  • 扩展2: 在Objctive-C中GCD提供了两种方式来支持dispatch队列的同步,就是dispatch组(队列组)和信号量(dispatch_semaphore)

 
 // 创建队列组
 dispatch_group_t group = dispatch_group_create();
 
 // 启动队列组中的block, 然后关联到队列组group中,
 

 队列组的block操作
 
 @param group 队列组
 @param queue#> 可以是全局的dispatch_get_global_queue(0, 0), 也可以是dispatch_get_main_queue()
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    
    NSLog(@"具体的网络请求操作");
});


 // 超时参数
 #define DISPATCH_TIME_NOW (0ull)   // 现在
 #define DISPATCH_TIME_FOREVER (~0ull)  // 一直
 
 // 表示: 等到group关联的block执行完毕

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

// 当group组执行完毕之后, 需要通知执行完毕, 那么就会使用到GCD中的dispatch_barrier_async函数
// 主要队列组中的操作执行完毕后就会调用这个函数中block
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
    NSLog(@"队列组中的操作执行完毕之后就会调用该函数");
});

//  我们也可以管理队列组的运行状态或者计数, 使用下面两个函数的时候需要注意的一点就是, 进入或者退出, 他们的次数必须要匹配.
dispatch_group_enter(group); // 进入
dispatch_group_leave(group);    // 退出
// 所以,我们也可以利用dispatch_group_enter、 dispatch_group_leave和dispatch_group_wait来实现同步


  • 信号量


 二、dispatch信号量(dispatch semaphore)
 
 1. 创建信号量,可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。
 
 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
 
 2. 等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。
 
 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
 
 3. 通知信号,如果等待线程被唤醒则返回非0,否则返回0。
 
 dispatch_semaphore_signal(semaphore);
 
 最后,还是回到生成消费者的例子,使用dispatch信号量是如何实现同步:

总结

目前开发App越来越多样化, 各种需求更是层出不穷, 所以很难想象在开发过程中你会遇到什么样的需求, 不过, 再变态的需求, 也会有解决的方法, 本章的知识点虽然在开发中比较常见, 但是也值得注意, 然而记录是学习最好的方法, 本章重在学习记录, 如果有什么问题需要请朋友们不吝赐教, 后续会有更多精彩的开发总结..........
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容