前言

对于iOS开发中的网络请求模块,AFNet的使用应该是最熟悉不过了,但你是否把握了网络请求正确的完成时机?本篇文章涉及线程同步、线程依赖、线程组等专用名词的含义,若对上述名词认识模糊,可先进行查阅理解后阅读本文。如果你也纠结于文中所述问题,可进行阅读希望对你有所帮助。大神无视勿喷。

在真实开发中,我们通常会遇到如下问题:

一、某界面存在多个请求,希望所有请求均结束才进行某操作。

对于这一问题的解决方案很容易想到通过线程组进行实现。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成,刷新界面");
    });

打印如下

1
2
3
4
Request_2
Request_1
Request_3
任务均完成,刷新界面

根据打印结果观察可能并没有什么问题,但需要注意的是Request_1、Request_2等在真实开发中通常对应为某个网络请求。而网络请求通常为异步,那这时是否还会有同样结果呢?

口说无凭,我们将NSLog(@"Request");部分替换为真正的网络请求。

对于App请求数据大部分人都会选择AFNetworking。使用AFN异步请求,请求的数据返回后,就刷新相关UI。如果某一个页面有多个网络请求,我们假设有三个请求,A、B、C,而且UI里的数据必须等到A、B、C全部完成后刷新后才显示。

这里我们书写一个网络请求通用方法,假设同时请求某新闻列表的3页数据,每页均为一个独立的网络请求。使用我们最常用的AFNet请求,方法如下(真实开发中可能为banner数据请求、主体网络请求、广告网络请求等):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)request_A {
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSDictionary *parameter = @{@"token":@"63104AB32427EBF89B957BBD1A5C5C11",
                                 @"page":@"1",
                                 @"upTime":@"desc"};
    [manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
        for (NSDictionary *rowsDict in dict[@"rows"]) {
            NSLog(@"A___%@",rowsDict[@"title"]);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    }];
}

request_B、request_C分别为请求第二页与第三页数据,这里不重复书写。为了显示更加明显,在请求中打印了对应新闻的标题内容。

运行打印如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
任务均完成,刷新界面
C___搞笑,不是认真的
C___摄影 | 街拍
C___生活小窍门
C___传统中国
C___想吃的美食系列
B___时间的见证者
B___没事 来吐槽吧……
B___触动心灵的摄影
B___摄影 | 黑白印记
B___每日插画推荐
A___左爱情,右面包
A___潮我看
A___世界各国的人们怎么过情人节
A___一点创意点亮生活
A___摄影 | 随手拍

运行后马上接收到了线程组完成的提示,之后数据才依次请求下来,很明显三个单纯的AFNet请求已经不能满足我们的需求了。线程组完成时并没有在我们希望的时候给予通知。在真实开发中会造成的问题为多个请求均加载完成,但界面已在未得到数据前提前刷新导致界面空白。

因此对于这种问题需要另辟蹊径,这里我们就要借助GCD中的信号量dispatch_semaphore进行实现,即营造线程同步情况。

dispatch_semaphore信号量为基于计数器的一种多线程同步机制。用于解决在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。

如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。

dispatch_semaphore_signal(semaphore)为计数+1操作。dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)为设置等待时间,这里设置的等待时间是一直等待。我们可以通俗的理解为单柜台排队点餐,计数默认为0,每当有顾客点餐,计数+1,点餐结束-1归零继续等待下一位顾客。比较类似于NSLock。

我们将网络请求通用方法进行修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)request_A {
    //创建信号量并设置计数默认为0
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];;
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSDictionary *parameter = @{@"token":@"63104AB32427EBF89B957BBD1A5C5C11",
                                 @"page":@"1",
                                 @"upTime":@"desc"};
    [manager POST:URL parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //计数+1操作
        dispatch_semaphore_signal(sema);
        NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
        for (NSDictionary *rowsDict in dict[@"rows"]) {
            NSLog(@"A___%@",rowsDict[@"title"]);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        计数+1操作
        dispatch_semaphore_signal(sema);
    }];
    //若计数为0则一直等待
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

为方便阅读,伪代码如下:

1
2
3
4
5
6
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

这时我们再运行程序,打印如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
C___聆听的耳朵
C___家居 | 你的家里还缺点什么?
C___logo设计
C___时装
C___搞笑,不是认真的
A___左爱情,右面包
A___潮我看
A___世界各国的人们怎么过情人节
A___一点创意点亮生活
A___摄影 | 随手拍
B___时间的见证者
B___没事 来吐槽吧……
B___触动心灵的摄影
B___摄影 | 黑白印记
B___每日插画推荐
任务均完成,刷新界面

运行打印可见,通过信号量dispatch_semaphore完美的解决了此问题,并且网络请求仍为异步,不会堵塞当前主线程。

二、某界面存在多个请求,希望请求依次执行。

对于这个问题通常会通过线程依赖进行解决,因使用GCD设置线程依赖比较繁琐,这里通过NSOperationQueue进行实现,这里采用比较经典的例子,三个任务分别为下载图片,打水印和上传图片,三个任务需异步执行但需要顺序性。代码如下,下载图片、打水印、上传图片仍模拟为分别请求新闻列表3页数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    //1.任务一:下载图片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];
    //2.任务二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];
    //3.任务三:上传图片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_C];
    }];
    //4.设置依赖
    [operation2 addDependency:operation1];      //任务二依赖任务一
    [operation3 addDependency:operation2];      //任务三依赖任务二
    //5.创建队列并加入任务
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

首先我们使用未添加信号量dispatch_semaphore时运行,打印如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
B___时间的见证者
B___没事 来吐槽吧……
B___触动心灵的摄影
B___摄影 | 黑白印记
B___每日插画推荐
A___潮我看
A___左爱情,右面包
A___世界各国的人们怎么过情人节
A___一点创意点亮生活
A___摄影 | 随手拍
C___盘
C___聆听的耳朵
C___家居 | 你的家里还缺点什么?
C___logo设计
C___时装

根据打印结果可见,若不对请求方法做处理,其运行结果并不是我们想要的,联系实际需求,A、B、C请求分别对应下载图片、打水印、上传图片,而此时运行顺序则为B->A->C,在未获得图片时即执行打水印操作明显是错误的。重复运行亦会出现不同结果,即请求不做处理,其结果不可控无法预测。线程依赖设置并未起到作用。

解决此问题的方法仍可通过信号量dispatch_semaphore进行解决。我们将请求方法替换为添加dispatch_semaphore限制的形式。即

1
2
3
4
5
6
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}]
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

再次重复运行,我们会发现每次运行结果均一致,A、B、C三任务异步顺序执行(A->B->C)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A___潮我看
A___左爱情,右面包
A___世界各国的人们怎么过情人节
A___一点创意点亮生活
A___摄影 | 随手拍
B___时间的见证者
B___没事 来吐槽吧……
B___触动心灵的摄影
B___摄影 | 黑白印记
B___每日插画推荐
C___盘
C___聆听的耳朵
C___家居 | 你的家里还缺点什么?
C___logo设计
C___时装

通过重复运行打印结果可证实确实实现了我们想要的效果。这样即解决了所提出的问题二。

后续

在开发中我们会遇到很多需要进行线程同步或请求同步的情况。比如弹出视图设置某功能,在点击确认按钮时发生请求,在请求成功同时销毁弹出视图等,均需要保证在请求真正完成时进行下一步操作。因此把握网络请求完成的正确时机还是很有必要的。

转载于:https://www.cnblogs.com/chglog/p/6782413.html

iOS开发——高级篇——线程同步、线程依赖、线程组相关推荐

  1. iOS开发——高级篇——iOS开发之网络安全密码学

    一.非对称加密 - RSA : + 公钥加密,私钥解密: + 私钥加密,公钥解密: + 只能通过因式分解来破解 二.对称加密 - DES - 3DES - AES (高级密码标准,美国国家安全局使用, ...

  2. iOS开发——高级篇——二维码的生产和读取

    一.二维码的生成 从iOS7开始集成了二维码的生成和读取功能 此前被广泛使用的zbarsdk目前不支持64位处理器 生成二维码的步骤: 导入CoreImage框架 通过滤镜CIFilter生成二维码 ...

  3. iOS开发多线程篇—线程安全

    一.多线程的安全隐患 资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块资源时,很容易引发数据错乱和数 ...

  4. iOS开发多线程篇—线程的状态

    iOS开发多线程篇-线程的状态 一.简单介绍 线程的创建: self.thread=[[NSThread alloc]initWithTarget:self selector:@selector(te ...

  5. iOS开发网络篇—多线程断点下载

    iOS开发网络篇-多线程断点下载 说明:本文介绍多线程断点下载.项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件.因为实现过程较为复杂,所以下面贴出完整的代码. 实现思路:下载开始, ...

  6. iOS开发网络篇—使用ASI框架进行文件下载

    iOS开发网络篇-使用ASI框架进行文件下载 说明:本文介绍iOS网络编程中经常用到的框架ASI,如何使用该框架进行文件的下载. 一.简单介绍 代码示例: 1 #import "YYView ...

  7. iOS开发多线程篇—多线程简介

    iOS开发多线程篇-多线程简介 一.进程和线程 1.什么是进程 进程是指在系统中正在执行的一个应用程序 每一个进程之间是独立的.每一个进程均执行在其专用且受保护的内存空间内 比方同一时候打开QQ.Xc ...

  8. iOS开发多线程篇—自定义NSOperation

    iOS开发多线程篇-自定义NSOperation 一.实现一个简单的tableView显示效果 实现效果展示: 代码示例(使用以前在主控制器中进行业务处理的方式) 1.新建一个项目,让控制器继承自UI ...

  9. iOS开发UI篇—UIWindow简单介绍

    iOS开发UI篇-UIWindow简单介绍 一.简单介绍 UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow iOS程序启动完毕后,创建的第一个视图控件就是UIWi ...

最新文章

  1. Linux下程序崩溃dump时的 core文件的使用方法
  2. mysql 索引 二叉树_MySQL 的索引,为什么是 B+而不是平衡二叉树
  3. java length()函数_小猿圈介绍java函数式编码结构及优势
  4. 2021下半年软考分数线会有变动吗?
  5. jq设置html的fontsize,Jquery 设置字体大小(font-size)与行高(line-height)
  6. wifi卡慢延迟高_家里WiFi特别卡,网络延迟高,可能不是网速的问题
  7. 烤鸡(洛谷-P2089)
  8. Go中对两个 nil 进行比较的结果是什么?
  9. 三、北京54坐标系与西安80坐标系
  10. 树莓派如何接硬盘_利用树莓派和闲置硬盘,搭建起家中的个人网盘
  11. 数字电路基础知识——CMOS门电路 (与非门、或非、非门、OD门、传输门、三态门)
  12. 中科院计算机研究方向-考研导师选择的一些建议
  13. linux开启nginx 443端口无效,http – Nginx正在侦听端口80或443但没有响应
  14. win10 查看版本信息(家庭版 专业版 企业版)
  15. 用旧手机搭建服务器并实现内网穿透不需要root(本人亲测很多次最简单的一个)
  16. 【大学生软件测试基础】图书阅读指南 - 决策表法
  17. sketch如何在手机中预览
  18. python制作简单动画_把数据摇起来!用Python制作动画可视化效果!
  19. url中的20%、22%、26%、7B%、%7D、28%、29%怎么解析还原成真实的字符
  20. Java虚拟机(一)Java虚拟机初步了解

热门文章

  1. python 中time, datetime的用法
  2. 转换成的jar文件接收后台的信息乱码cmd解决办法
  3. T2 Funcin T1,out T2(T1 arg)
  4. Android国际化-补充
  5. Java回顾之JDBC
  6. mongoose数据查询or、and、where等用法
  7. 中心极限与大数定理律的关系_多元函数的极限、连续性分析
  8. 利息高的贷款通过率会高一些吗?
  9. Google Analytics是什么
  10. 旅游中用稳定器和相机拍视频是怎样的体验?