IOS基础网络:请求方法(下)
原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 八、文件流下载
- 九、拼接URL进行网络请求
- 1、计算签名
- 2、计算签名的代码实现
- 3、MD5 加密算法
- 4、拼接请求参数的URL
- 5、实现网络请求方法
- 6、调用网络请求方法
- 十、实现同步请求
- 1、使用信号量
- 2、使用锁
- Demo
- 参考文献
七、文件流下载
运行效果
2021-02-22 17:10:45.258952+0800 NetworkRequestDemo[21602:747813] 下载到文件路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/5BC32A40-EDB6-4954-A93D-DE1741EFFB53/data/Containers/Data/Application/7E3E2A24-2CF1-4E04-B364-179B6185BC84/Documents/video.mp4
2021-02-22 17:10:45.259484+0800 NetworkRequestDemo[21602:747813] 下载完成
工具类提供的网络请求方法
- (NSURLSessionDataTask *)getDownFileUrl:(NSString *)fileUrl backBlock:(fileHandleBlock)handleBlock
{
if (fileUrl == nil || handleBlock == nil)
{
return nil;
}
self.handleBlock = handleBlock;
NSURL *requestUrl = [NSURL URLWithString:fileUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl];
request.HTTPMethod = @"GET";
request.timeoutInterval = 30.0;
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
return dataTask;
}
NSURLSessionDataDelegate
接收到服务器的响应:http
的head
数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
fileTotalProgress = response.expectedContentLength;
completionHandler(NSURLSessionResponseAllow);
}
接收到http
的body
数据。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
...
}
下面这种直接拼接的方式会造成内存暴增导致APP崩溃:
[self.receiveData appendData:data];
创建输出流。append
为YES
的话,每次写入都是追加到文件尾部。不过这个outpustream
好像没用上。
self.outpustream = [NSOutputStream outputStreamToFileAtPath:[self getSaveFilePath] append:YES];
细水流长,一点一点地将数据流存入到文件中。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saveFile:data];
});
计算下载进度:
fileDownProgress = fileDownProgress + data.length;
float downProgress = fileDownProgress/(fileTotalProgress * 1.0) * 100;
NSString *progress = [NSString stringWithFormat:@"%.2f%@",downProgress,@"%"];
NSURL *saveFileURL = [NSURL URLWithString:[self getSaveFilePath]];
self.handleBlock(saveFileURL, progress);
将下载的数据流一点点写入文件
- (void)saveFile:(NSData *)data
{
// 保存文件的路径
NSString *filePath = [self getSaveFilePath];
// 如果文件不存在,返回的是nil
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
// 判断文件存不存在
if (fileHandle == nil)
{
// 如果文件不存在,会自动创建文件
[data writeToFile:filePath atomically:YES];
}
else
{
// 让offset指向文件的末尾
[fileHandle seekToEndOfFile];
// 在文件的末尾再继续写入文件
[fileHandle writeData:data];
// 同步一下防止操作混乱
[fileHandle synchronizeFile];
// 关闭文件
[fileHandle closeFile];
}
}
获取文件存储路径
- (NSString *)getSaveFilePath
{
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"video.mp4"];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
return filePath;
}
点击下载按钮进行下载
- (void)downloadButtonClicked
{
__weak typeof(self) weakSelf = self;
FileStreamNetworkTool *fileStream = [FileStreamNetworkTool new];
[fileStream getDownFileUrl:@"https://pic.ibaotu.com/00/48/71/79a888piCk9g.mp4" backBlock:^(NSURL *fileUrl,NSString *progress) {
weakSelf.progressLabel.text = progress;
if (fileUrl)
{
NSLog(@"下载到文件路径为:%@",[fileUrl absoluteString]);
}
}];
}
九、拼接URL进行网络请求
整个流程分为计算签名-->拼接URL
-->再进行网络请求三部分,将其封装在一个请求方法中,传入参数和相对路径的URL
即可拿到返回数据,其中返回数据的方式是通过block
属性来进行的。
// 网络请求方法
- (void)startRequest:(NSDictionary *_Nullable)parameters pathUrl:(NSString *)pathUrl
{
...
}
1、计算签名的解释
为什么要有计算签名这个步骤
网络传输并非绝对安全可靠。以微信支付举例,一旦支付请求被中间人拦截并恶意篡改(如利用DNS欺骗),就会画风突变。这种场景下就需要信息摘要技术了。信息摘要把明文内容按某种规则生成一段哈希值,即使明文消息只改动了一点点,生成的结果也会完全不同。MD5 (Message -digest algorithm 5)
就是信息摘要的一种实现,它可以从任意长度的明文字符串生成128位的哈希值。
生成摘要哈希的正确姿势是什么样呢
- 收集参数,这里以金额和目标账户举例
- 按照规则,把参数名和参数值拼接成一个字符串,同时把给定的密钥也拼接起来。之所以需要密钥,是因为攻击者也可能获知拼接规则
- 利用 MD5 算法,从原文生成哈希值。MD5 生成的哈希值是 128 位的二进制数,也就是 32 位的十六进制数。
MD5算法生成签名需要的东西
服务商一般会给你一个appid
,appkey
。同时这两个参数服务商也会保存,这两个形成了你的唯一标识。appid
通过网络传输,而appkey
是不在网络上进行传输的,只在生成签名时使用,所以安全性还是比较高的。
MD5算法生成签名的流程
- 除去加密数组中的空值和签名参数
- 对数组排序
- 把数组所有元素,按照
参数=参数值
的模式用&
字符拼接成字符串 - 加上
appkey
值,对形成的数据进行MD5
加密,生成签名
第三方支付平台如何验证请求的签名
- 发送方和请求方约定相同的字符串拼接规则,约定相同的密钥。
- 第三方平台接到支付请求,按规则拼接业务参数和密钥,利用 MD5 算法生成
Sign
。 - 用第三方平台自己生成的
Sign
和请求发送过来的Sign
做对比,如果两个Sign
值一模一样,则签名无误,如果两个Sign
值不同,则信息做了篡改。这个过程叫做验签。
2、计算签名的代码实现
创建一个可变字典
创建一个可变字典,用来容纳用于计算签名的各种参数,首先放入cid
,注意cid
可以先写死,但是要带入计算部分。
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"cid"] = @"000001";
放入用户标识uid
接着放入用户标识uid
,uid
从沙盒中获取,第一次登陆后存储uid
到本地沙盒,并且保存登录状态,其中保存的方法如下。
// content为网络请求返回的内容
NSString *uid = content[@"uid"];
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [path stringByAppendingPathComponent:@"uid.plist"];
NSMutableDictionary *plistDict = [[NSMutableDictionary alloc] init];
[plistDict setValue:uid forKey:@"uid"];// 存储uid到本地沙盒
[plistDict setValue:@(1) forKey:@"statusCode"]; // 保存登录状态,防止跳转界面时重复登录
[plistDict writeToFile:filePath atomically:YES];
从沙盒中获取uid
放入用于计算签名的字典。
//uid用户标识
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [path stringByAppendingPathComponent:@"uid.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
dict[@"uid"] = plistDict[@"uid"];
将请求参数放入用于计算签名的字典中
将请求参数放入用于计算签名的字典中。首先需要将请求参数进行序列化为JSON
数据,接着将NSData
数据以utf-8
解码为字符串,但是注意如果NSData
数据受损,则会返回nil
,最后字符串放入用于计算签名的字典中。
if (parameters.count > 0)
{
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
NSString *qValue = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
dict[@"q"] = qValue;
}
计算签名
前面的准备工作都是为了这一步的计算签名。首先我们要根据用于计算签名的字典中元素个数创建个可变数组,用于放入用=
拼接字典的key
和value
后字符串。
NSMutableArray *dictArray = [NSMutableArray arrayWithCapacity:dict.count];
for (NSString *key in dict.allKeys)
{
NSString *string = [NSString stringWithFormat:@"%@=%@", key, dict[key]];
[dictArray addObject:string];
}
接着对放入数组中的字符串进行排序,将排好序的字符串用";"
进行拼接成为一个完整的字符串。
NSArray *array = [dictArray sortedArrayUsingSelector:@selector(compare:)];
NSString *signStr = [array componentsJoinedByString:@";"];
最后在这个字符串中放入我们的签名密钥。
// 密钥可以写在这
signStr = [NSString stringWithFormat:@"%@%@", signStr, @"mUwmfk6viCFCWydSogtH"];
大功告成!将这个计算后的签名字符串加密后放入之前我们创建的用于容纳计算签名的各种元素的字典中,作为其中一个元素存在。
dict[@"sign"] = [self doMD5String:signStr];
3、MD5 加密算法
注意到,这里使用了MD5进行加密,先引入MD5加密所需的框架:
#import <CommonCrypto/CommonCrypto.h>
辅助常量:
@property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER;
typedef uint32_t CC_LONG; /* 32 bit unsigned integer */
extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md);
#define CC_MD5_DIGEST_LENGTH 16 /* digest length in bytes */
核心算法:
- (NSString *)doMD5String:(NSString *)string
{
//1: 将字符串转换成C语言的字符串(因为:MD5加密是基于C的)
const char *cStr = [string UTF8String];
//2: 初始化一个字符串数组,用来存放MD5加密后的数据
unsigned char result[CC_MD5_DIGEST_LENGTH];
//3: 计算MD5的值
//参数一: 表示要加密的字符串
//参数二: 表示要加密字符串的长度
//参数三: 表示接受结果的数组
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
// 从保存结果的数组中,取出值进行加密
int first = abs([self bytesToInt:result offset:0]);
int second = abs([self bytesToInt:result offset:4]);
int third = abs([self bytesToInt:result offset:8]);
int fourth = abs([self bytesToInt:result offset:12]);
// 将加密后的值拼接后赋值给字符串
NSString *stirng = [NSString stringWithFormat:@"%d%d%d%d", first, second, third, fourth];
// 返回加密结果
return stirng;
}
其中用到的bytesToInt
方法如下:这个设计到MD5 算法底层原理,比较复杂略过。
// 从保存结果的数组中,取出值进行加密
- (int)bytesToInt:(Byte[])src offset:(int)offset
{
int value;
value = (int)(((src[offset] & 0xFF) << 24) | ((src[offset + 1] & 0xFF) << 16) | ((src[offset + 2] & 0xFF) << 8) |
(src[offset + 3] & 0xFF));
return value;
}
4、拼接请求参数的URL
❶ 首先还是先生成请求参数字符串,第一个放入的还是我们的cid
。
//cid 只拼接了一次,上次是用于计算签名
NSString *parametersURL = @"cid=000001";
❷ 接着放入我们网络请求方法中传入的参数,这是我们的主体部分。
if (dict[@"q"])
{
parametersURL = [parametersURL stringByAppendingFormat:@"&q=%@",[dict[@"q"] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
}
此处存在一个小问题,因为网络请求会拼接中文参数,用户名登陆等很多地方会用到中文,所以需要针对中文进行编码和解码,举两个比较清晰的例子:
编码:
NSString* hStr =@"你好啊";
NSString* hString = [hStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSLog(@"hString === %@",hString); // hString === %E4%BD%A0%E5%A5%BD%E5%95%8A
解码:
NSString*str3 =@"\u5982\u4f55\u8054\u7cfb\u5ba2\u670d\u4eba\u5458\uff1f";
NSString*str5 = [str3 stringByRemovingPercentEncoding];
NSLog(@"str5 ==== %@",str5);// str5 ==== 如何联系客服人员?
❸ 然后放入我们的uid
,注意uid
当我们第一次登录的时候是不存在的,第一次登录后才保存到了沙盒中。
if (dict[@"uid"])
{
parametersURL = [parametersURL stringByAppendingFormat:@"&uid=%@",dict[@"uid"]];
}
❹ 最后放入我们千辛万苦得来的签名。
parametersURL = [parametersURL stringByAppendingFormat:@"&sign=%@", dict[@"sign"]];
❺ 完美,万事俱备,只欠东风。将请求URL
的共同部分提取出来作为baseURL
即这里的https://apiproxytest.ucarinc.com/ucarincapiproxy/action/th/api
,然后将我们本次请求方法中传入的具体url
拼接到baseURL
后面,最后再放入我们历经九九八十一难得到的无上至宝——parametersURL
,注意到这里使用的是GET
的请求方法。
NSString *strURL = [NSString stringWithFormat:@"https://apiproxytest.ucarinc.com/ucarincapiproxy/action/th/api%@?%@",pathUrl,parametersURL];
NSLog(@"%@",strURL);
❻ 将请求URL
字符串转变为NSURL
类型,打完收工。
NSURL *url = [NSURL URLWithString:strURL];
5、实现网络请求方法
这里使用系统的NSURLSession
来实现简单的GET
请求。
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *resDict;
NSLog(@"请求完成...");
if (!error) {// 请求成功
// 此处返回的数据是JSON格式的,因此使用NSJSONSerialization进行反序列化处理,解析服务器返回的数据
resDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
// 网络请求数据在其他线程,拿到数据后需要返回主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// block属性,用于回传数据
self.transDataBlock(resDict);
});
} else {// 请求失败
NSLog(@"error: %@", error.localizedDescription);
}
}];
[task resume];
其中的block
属性如下:
typedef void (^TransDataBlock)(NSDictionary * content);
@property (copy, nonatomic) TransDataBlock transDataBlock;
6、调用网络请求方法
既然我们实现了刚才的网络请求方法,那么在实际的业务场景中我们应该怎么去调用它呢?
❶ 首先创建我们的请求参数,例如:
NSArray *keysArr = [[NSArray alloc] initWithObjects:@"telephone",@"password",nil];
NSArray *valuesArr = [[NSArray alloc] initWithObjects:self.phoneNumber,self.password,nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjects:valuesArr forKeys:keysArr];
❷ 接着通过URLRequest
传入请求参数和相对地址发起网络请求,再使用block
的方式拿到请求结果处理业务逻辑,注意避免循环引用,要使用__weak
修饰符。
self.urlRequest = [[URLRequest alloc] init];
__weak LoginViewController *weakSelf = self;
self.urlRequest.transDataBlock = ^(NSDictionary * _Nonnull content) {
}
[self.urlRequest startRequest:dict pathUrl:@"/home/login"];
❸ 处理业务逻辑之前,首先需要先判断响应结果的状态码是否正常,这里的测试接口比较特殊所以判断了两次,一般一次就OK了。
NSInteger errorStatusCode = [content[@"code"] integerValue];
if (errorStatusCode == 1) {
NSDictionary *dict = [NSDictionary dictionaryWithDictionary:content [@"content"]];
weakSelf.statusCode = [dict[@"code"] integerValue];
if (self.statusCode == 1) {
} else {
NSLog(@"%@",content[@"msg"]);
}
}
❹ 为了完整,把业务逻辑也列举出来下。
//获取接口数据
NSDictionary *memberRe = [NSDictionary dictionaryWithDictionary:dict[@"re"]];
//保存登录状态,获取uid.....
//通过接口刷新Tab的badgeValue的值.....
delegate.shoppingCartNavigationController.tabBarItem.badgeValue = num;
//刷新页面
[weakSelf.tableView reloadData];
十、实现同步请求
在一般的需求中,我们会使用异步请求来进行数据交换,等待数据返回之后再进行回调操作,执行所需要的操作。这种方式的好处是,不需要阻塞线程来等待请求结果。但是在一些特殊的场景中我们需要使用同步等待数据的方式来获取数据,例如阿里云的oss
中获取token
就是这种需求。
1、使用信号量
信号量可以理解为是一个资源计数器,对信号量有两个操作来达到互斥,分别是P
和V
操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程A
运行时,使用资源,进行P
操作,即对信号量值减1,也就是资源数少了1个。这时信号量值为0。系统中规定当信号量值为0是,必须等待,直到信号量值不为零才能继续操作。 这时如果进程B
想要运行,那么也必须进行P
操作,但是此时信号量为0,所以无法减1,即不能P
操作,也就阻塞。这样就达到了进程A
的排他访问。 当进程A
运行结束后,释放资源,进行V
操作。资源数重新加1,这时信号量的值变为1,这时进程B
发现资源数不为0,信号量能进行P
操作了,立即执行P
操作。信号量值又变为0,进程B
有资源,其余线程必须等到,达到线程B
的排他访问。 这就是信号量来控制线程互斥的原理。在iOS中,信号量的实现依赖以下函数:
dispatch_semaphore_create(long value); // 创建一个semaphore
dispatch_semaphore_signal(dispatch_semaphore_t dsema); // 发送一个信号
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等到信号
第一个函数有一个长整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal
是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait
等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,便可以实现一个"同步的请求":
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
__block id _responseObject = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
_responseObject = responseObject;
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
_responseObject = nil;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"获取到结果:%@", _responseObject);
这里有一个小问题需要注意一下:dispatch_semaphore_wait
这个方法是会阻塞线程的,而AFN
的回调代码默认是在主队列中进行的,所以这个操作如果放在主线程中会造成死锁:dispatch_semaphore_wait
阻塞了主线程,需要等待信号来解除线程阻塞, 在线程阻塞接触之前,该线程不能继续执行任务。但是如果回调也在主队列中,由于主线程已经被阻塞,所以回调中的操作永远不会执行,造成主线程被永久阻塞。所以解决方案也有两种,一是将该操作放在非主线程中进行:例如将请求放在自定义队列中:
dispatch_queue_t queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_async(queue, ^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
__block id _responseObject = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
_responseObject = responseObject;
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
_responseObject = nil;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"获取到结果:%@", _responseObject);
});
设置回调操作队列为非主队列:
manager.completionQueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
2、使用锁
在多线程开发中,锁是专门用来控制资源竞争访问权限的工具,以确保在同一时间最多只能有一个线程对资源进行访问,以确保资源的完整性。因此,我们也可以使用锁来实现同步请求操作。
这里我们选择NSCondition
锁来做实现NSCondition
锁有两个主要的功能:锁定和监测。在添加锁时可以监测是否满足指定条件,如果不满足可以使用wait
方法阻塞线程,等待signal
或者broadcast
信号,收到信号后会再次尝试加锁,如果加锁成功,线程阻塞解除,否则继续阻塞。使用NSCondition
来实现同步的代码大概这个样子:
NSCondition *lock = [[NSCondition alloc] init];
__block NSDictionary *_responseObject = nil;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[lock lock];
_responseObject = responseObject;
[lock signal];
[lock unlock];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[lock lock];
_responseObject = @{};
[lock signal];
[lock unlock];
}];
[lock lock];
if (!_responseObject) {
[lock wait];
}
[lock unlock];
NSLog(@"responseObject == %@", _responseObject);
同样地,尽量不要将该请求操作放在主线程中,这样回调线程(主线程)会通过发送信号激活阻塞当前线程;如果非要放在主线程中,那就需要设置请求的回调(其实是发送信号的操作)在其他线程中执行,也可以使用NSConditionLock
来进行实现:
dispatch_queue_t queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_async(queue, ^{
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
__block NSDictionary *_responseObject = nil;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[lock lock];
_responseObject = responseObject;
[lock unlockWithCondition:1];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[lock lock];
_responseObject = @{};
[lock unlockWithCondition:1];
}];
[lock lockWhenCondition:1];
NSLog(@"responseObject == %@", _responseObject);
[lock unlock];
});
Demo
Demo在我的Github上,欢迎下载。
BasicsDemo