iOSiOS 你不知道的新鲜事iOS 开发成长中心

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

2017-02-15  本文已影响144人  Alexander

前言

最近项目在接入网络接口时, 有个比较值得注意的地方, 就是一个界面存在多个网络接口, 比如: 在首页界面中存在多个网络请求接口(包括: 图片轮播器, 黄金产品以及黄金样品保单). 如果按照平常那种思维来编码, 很可能会出现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不显示.最后导致界面空白无物.

解决方法: 根据上述的现象, 这里需要引进另一个函数
 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中的信号量可以解决多个操作共用同一资源时, 造成主线程阻塞的问题.
知识扩展
> 扩展: 如果我们要指定网络操作的执行顺序的话, 直接使用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];


 
 // 创建队列组
 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越来越多样化, 各种需求更是层出不穷, 所以很难想象在开发过程中你会遇到什么样的需求, 不过, 再变态的需求, 也会有解决的方法, 本章的知识点虽然在开发中比较常见, 但是也值得注意, 然而记录是学习最好的方法, 本章重在学习记录, 如果有什么问题需要请朋友们不吝赐教, 后续会有更多精彩的开发总结..........
上一篇 下一篇

猜你喜欢

热点阅读