纯NSURLSession实现网络请求
最近搞IM项目,需要尽量减少三方库的使用,抽时间写了一个NSURLSession的工具类,目前支持GET, POST, 单张或多张图片上传,单文件上传与下载。
序言
于2013年的WWDC上发布,相对NSURLConnection的优点:
◎ 支持 http2.0 协议,
◎ 在处理下载任务的时候可以直接把数据下载到磁盘
支持后台下载,上传,
◎ 同一个 session 发送多个请求,只需要建立一次连接(复用了TCP),
◎ 提供了全局的 session 并且可以统一配置,使用更加方便,
◎ 下载的时候是多线程异步处理,效率更高,
主要使用的类:
NSURLSessionTask为抽象类,使用其子类
1, GET,POST -> NSURLSessionDataTask
2, 上传 -> NSURLSessionUploadTask
3, 下载 -> NSURLSessionDownloadTask
GET,POST
上代码,GET,POST请求比较简单,我是采用构建请求体方法,这样可以细化设置请求的各项参数,例如超时时间,需要注意当url含有中文字符时需要进行编码,否则转换成的NSURL为nil。
#pragma mark - 构建GET,POST的初始请求体
- (NSMutableURLRequest *)requestUrlString:(NSString *)urlString method:(NSString *)method body:(NSDictionary *)body {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
if (networkRequestBaseUrlString.length) {
urlString = [NSString stringWithFormat:@"%@/%@",networkRequestBaseUrlString,urlString];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:method];
[request setTimeoutInterval:networkRequestTimeoutInterval];
[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"UTF-8" forHTTPHeaderField:@"Charset"];
if (body.allValues.count) {
NSString *bodyStr = [self createJsonString:body];
[request setHTTPBody:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
}
[self debugLog:NSStringFormat(@"请求头:\n%@\n",[request allHTTPHeaderFields])];
[self debugLog:NSStringFormat(@"请求体: %@ %@\n%@\n\n",method, urlString, body)];
return request;
}
#pragma mark - GET请求
- (NSURLSessionTask *)GET:(NSString *)urlString
parameters:(NSDictionary *)parameters
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[self requestUrlString:urlString method:@"GET" body:parameters] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *jsonSerializationError = nil;
NSDictionary *jsonDict = nil;
if (data) {
jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
}
if (error || jsonSerializationError) {
if (failure) {
NSError *failureError = error ? :JsonSerializationError;
failure(failureError);
[self debugLog:NSStringFormat(@"%@%@",networkRequestErrorDesc,failureError.description)];
}
}else {
if (success) {
success(jsonDict);
[self debugLog:NSStringFormat(@"GET请求成功 response: %@",jsonDict)];
}
}
});
}];
[dataTask resume];
return dataTask;
}
#pragma mark - POST请求
- (NSURLSessionTask *)POST:(NSString *)urlString
parameters:(NSDictionary *)parameters
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[self requestUrlString:urlString method:@"POST" body:parameters] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *jsonSerializationError = nil;
NSDictionary *jsonDict = nil;
if (data) {
jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
}
if (error || jsonSerializationError) {
if (failure) {
NSError *failureError = error ? :JsonSerializationError;
failure(failureError);
[self debugLog:NSStringFormat(@"%@%@",networkRequestErrorDesc,failureError.description)];
}
}else {
if (success) {
success(jsonDict);
[self debugLog:NSStringFormat(@"POST请求成功 response: %@",jsonDict)];
}
}
});
}];
[dataTask resume];
return dataTask;
}
文件上传
要实现POST上传文件,苹果没有做任何封装,需要安照 W3C 指定的标准格式拼接表单,多文件上传和单文件上传的基本思路是一样的,唯一的区别在于对请求体的封装,下面列举两种方式:
1,多文件的请求体部分格式1
{
// 第一个文件参数的上边界
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=美女\r\n
Content-Type:image/jpeg\r\n\r\n
上传文件的二进制数据部分
// 第一个文件参数的下边界
\r\n--boundary--
// 第二个文件参数的上边界
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n
Content-Type:text/plain\r\n\r\n
上传文件的二进制数据部分
// 第二个文件参数的下边界
\r\n--boundary--
}
2,多文件上传的请求体格式2
{
// 上边界
// 第一个文件参数
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=美女\r\n
Content-Type:image/jpeg\r\n\r\n
上传文件的二进制数据部分
// 第二个文件参数
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n
Content-Type:text/plain\r\n\r\n
上传文件的二进制数据部分
// 下边界
\r\n--boundary--
}
}
3,多文件 + 普通文本上传
{
* 有些服务器可以在上传文件的同时,提交一些文本内容给服务器
* 典型应用:
<1>新浪微博: 上传图片的同时,发送一条微博信息!
<2>购物评论: 购买商品之后发表评论的时候图片+评论内容!
多文件上传的数据格式3
{
Content-Type: multipart/form-data; boundary=boundary
// ------ 以下内容,是提供给服务器的二进制数据格式
--boundary\r\n
Content-Disposition: form-data; name="userfile[]"; filename="aaa.txt"\r\n
Content-Type: application/octet-stream\r\n\r\n
文件二进制数据
\r\n
--boundary\r\n
Content-Disposition: form-data; name="userfile[]"; filename="aaa副本.txt"\r\n
Content-Type: application/octet-stream\r\n\r\n
文件二进制数据
\r\n
--boundary\r\n
// username 是脚本文件接收参数的名称
Content-Disposition: form-data; name="username"\r\n\r\n
普通文本二进制数据
\r\n
--boundary--
// ------
以上部分,是发送给服务器的二进制数据的组成格式(示例)
}
static NSString *const kBoundary = @"boundary";
上传需要的参数
/**
* 上传文件
*
* @param urlString 请求地址
* @param parameters 请求参数
* @param name 文件对应服务器上的字段
* @param filePath 文件本地的沙盒路径
* @param progress 上传进度信息
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
- (NSURLSessionTask *)uploadFileWithURL:(NSString *)urlString
parameters:(NSDictionary *)parameters
name:(NSString *)name
filePath:(NSString *)filePath
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure
#pragma mark - 首先构建上传下载的初始请求
- (NSMutableURLRequest *)requestWithUrlString:(NSString *)urlString
cachePolicy:(NSURLRequestCachePolicy)cachePolicy
timeoutInterval:(NSTimeInterval)timeoutInterval {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//设置忽略缓存与超时时间
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[[NSURL alloc]initWithString:urlString] cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
request.HTTPMethod = @"POST";
request.allHTTPHeaderFields = @{
@"Content-Type":[NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBoundary]
};
return request;
}
#pragma mark - 返回本地路径文件的NSURLResponse(获取本地路径文件的类型与文件名称)
- (NSURLResponse *)responseWithLocalFileUrl:(NSString *)urlString {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
NSURL *url = [NSURL fileURLWithPath:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__block NSURLResponse *localResponse = nil;
// 使用信号量实现NSURLSession同步请求
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
localResponse = response;
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return localResponse;
}
上传文件的时候,需要告诉服务器文件类型(即Content-Type),这时,需要获取文件的 MIMEType.
使用上方方法即可。如果不想告诉服务器具体的文件类型,可以使用这个 Content-Type : application/octet-stream(8进制流)
常见的 Content-Type 类型:
{
- 大类型/小类型
- text/plain
- image/jpg
- image/png
- image/gif
- text/html
- application/json
}
#pragma mark - 拼接传入body内的文件部分表单
- (NSData *)bodyDataWithParameters:(NSDictionary *)parameters name:(NSString *)name
filePath:(NSString *)filePath {
//按照W3C格式构建上传数据
NSMutableData *bodyData = [self parametersData:parameters];
//获取本地路径文件的类型与文件名称
NSURLResponse *response = [self responseWithLocalFileUrl:filePath];
NSString *contentType = response.MIMEType;
NSString *filename = response.suggestedFilename;;
//设置服务器接收名称与文件名称
NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition:form-data; name=\"%@\"; filename=\"%@\";Content-Type=%@\r\n\r\n",kBoundary,name,filename,contentType];
[bodyData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
//加入文件的数据
[bodyData appendData:fileData];
//下边界
NSString *lowerBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n",kBoundary];
[bodyData appendData:[lowerBoundary dataUsingEncoding:NSUTF8StringEncoding]];
return bodyData;
}
#pragma mark - 拼接传入body内的参数部分表单(传入的类型可能为字符串,图片,二进制数据)
- (NSMutableData *)parametersData:(NSDictionary *)parameters {
//按照W3C格式构建上传数据
NSMutableData *parametersData = [[NSMutableData alloc]init];
[parameters enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL * _Nonnull stop) {
//上边界
NSString *upperBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",kBoundary];
[parametersData appendData:[upperBoundary dataUsingEncoding:NSUTF8StringEncoding]];
//拼接主体内容
NSString *pair = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
[parametersData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
//根据字典value类型追加不同数据到body
if ([value isKindOfClass:[NSString class]]) {
[parametersData appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
}else if ([value isKindOfClass:[NSNumber class]]) {
NSString *numStr = [NSString stringWithFormat:@"%@",value];
[parametersData appendData:[numStr dataUsingEncoding:NSUTF8StringEncoding]];
}
else if ([value isKindOfClass:[NSData class]]){
[parametersData appendData:value];
}else if ([value isKindOfClass:[UIImage class]]) {
[parametersData appendData:UIImageJPEGRepresentation(value, 1.0f)];
}
//换行追加下一条数据
[parametersData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
return parametersData;
}
#pragma mark - 执行上传文件操作
- (NSURLSessionTask *)uploadFileWithURL:(NSString *)urlString
parameters:(NSDictionary *)parameters
name:(NSString *)name
filePath:(NSString *)filePath
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//设置忽略缓存与超时时间
NSMutableURLRequest *request = [self requestWithUrlString:urlString cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:networkRequestUploadTimeoutInterval];
NSData *bodyData = [self bodyDataWithParameters:parameters name:name filePath:filePath];
request.HTTPBody = bodyData;
[request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)bodyData.length] forHTTPHeaderField:@"Content-Length"];
NSURLSessionTask *uploadTask = [self.urlSession uploadTaskWithRequest:request fromData:nil completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *jsonSerializationError = nil;
NSDictionary *jsonDict = nil;
if (data) {
jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
}
if (error || jsonSerializationError) {
if (failure) {
NSError *failureError = error ? :JsonSerializationError;
failure(failureError);
[self debugLog:NSStringFormat(@"%@%@",networkRequestErrorDesc,failureError.description)];
}
}else {
if (success) {
success(jsonDict);
[self debugLog:NSStringFormat(@"上传文件成功 response: %@",jsonDict)];
}
}
});
}];
self.uploadFileTask = uploadTask;
self.uploadFileProgressCallback = progress;
[uploadTask resume];
return uploadTask;
}
#pragma mark - 监听上传进度(所有的进度监听统一使用一个urlSession管理)
_urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
/**
bytesSent 本次发送的字节数
totalBytesSent 总共发送的字节数
totalBytesExpectedToSend 文件的总大小
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
float progress = (float) totalBytesSent / totalBytesExpectedToSend;
if (self.uploadFileTask == task &&
self.uploadFileProgressCallback) {
self.uploadFileProgressCallback(progress);
[self debugLog:[NSString stringWithFormat:@"上传文件进度: %.2f",progress]];
}else if (self.uploadImagesTask == task &&
self.uploadImagesProgressCallback) {
self.uploadImagesProgressCallback(progress);
[self debugLog:NSStringFormat(@"上传图片进度: %.2f",progress)];
}
}
上传单/多张图片
上传需要的参数
/**
* 上传单/多张图片
*
* @param urlString 请求地址
* @param parameters 请求参数
* @param name 图片对应服务器上的字段
* @param images 图片数组
* @param fileNames 图片文件名数组, 可以为nil, 数组内的文件名默认为当前日期时间"yyyyMMddHHmmss"
* @param imageScale 图片文件压缩比 范围 (0.f ~ 1.f)
* @param imageType 图片文件的类型,例:png、jpg(默认类型)....
* @param progress 上传进度信息
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
- (NSURLSessionTask *)uploadImagesWithURL:(NSString *)urlString
parameters:(NSDictionary *)parameters
name:(NSString *)name
images:(NSArray<UIImage *> *)images
fileNames:(NSArray<NSString *> *)fileNames
imageScale:(CGFloat)imageScale
imageType:(NSString *)imageType
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure;
执行上传操作,注意图片名称与图片的匹配,图片要进行压缩处理
- (NSURLSessionTask *)uploadImagesWithURL:(NSString *)urlString
parameters:(NSDictionary *)parameters
name:(NSString *)name
images:(NSArray<UIImage *> *)images
fileNames:(NSArray<NSString *> *)fileNames
imageScale:(CGFloat)imageScale
imageType:(NSString *)imageType
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
NSMutableURLRequest *request = [self requestWithUrlString:urlString cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:networkRequestUploadTimeoutInterval];
NSMutableData *bodyData = [self parametersData:parameters];
if (images.count != fileNames.count) {
NetworkRequestDebugLog(@"图片名称与图片数组总数不一致!");
return nil;
}
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSData *imageData = UIImageJPEGRepresentation(images[idx], imageScale ?: 1.f);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *imageFileName = NSStringFormat(@"%@%d.%@",str,(int)idx,imageType.length ?imageType:@"jpg");
imageFileName = fileNames.count ? NSStringFormat(@"%@.%@",fileNames[idx],imageType.length? imageType:@"jpg") : imageFileName;
NSString *mimeType = NSStringFormat(@"image/%@",imageType.length ? imageType: @"jpg");
NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition:form-data; name=\"%@\"; filename=\"%@\";Content-Type=%@\r\n\r\n",kBoundary,name,imageFileName,mimeType];
[bodyData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
[bodyData appendData:imageData];
//换行追加下一条数据
[bodyData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
//下边界
NSString *lowerBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n",kBoundary];
[bodyData appendData:[lowerBoundary dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = bodyData;
[request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)bodyData.length] forHTTPHeaderField:@"Content-Length"];
NSURLSessionTask *uploadTask = [self.urlSession uploadTaskWithRequest:request fromData:nil completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *jsonSerializationError = nil;
NSDictionary *jsonDict = nil;
if (data) {
jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
}
if (error || jsonSerializationError) {
if (failure) {
NSError *failureError = error ? :JsonSerializationError;
failure(failureError);
[self debugLog:NSStringFormat(@"%@%@",networkRequestErrorDesc,failureError.description)];
}
}else {
if (success) {
success(jsonDict);
[self debugLog:NSStringFormat(@"上传图片成功 response: %@",jsonDict)];
}
}
});
}];
self.uploadImagesTask = uploadTask;
self.uploadImagesProgressCallback = progress;
[uploadTask resume];
return uploadTask;
}
下载文件
上传需要的参数
/**
* 下载文件
*
* @param urlString 请求地址
* @param fileDir 文件存储目录
* @param progress 文件下载的进度信息
* @param success 下载成功的回调(回调参数filePath:文件的路径)
* @param failure 下载失败的回调
*
* @return 返回NSURLSessionDownloadTask实例,可用于暂停继续,暂停调用suspend方法,开始下载调用resume方法
*/
- (NSURLSessionDownloadTask *)downloadWithURL:(NSString *)urlString
fileDir:(NSString *)fileDir
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure;
#pragma mark - 执行下载操作
注意这里不能直接调用 self.urlSession downloadTaskWithRequest:downloadRequest completionHandler 需要去掉completionHandler 在代理方法中移动文件与监听进度。
- (NSURLSessionDownloadTask *)downloadWithURL:(NSString *)urlString
fileDir:(NSString *)fileDir
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//NSURLRequestReloadRevalidatingCacheData 验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:networkRequestTimeoutInterval];
//下载完成后获取数据 此时已经自动缓存到本地,下次会直接从本地缓存获取,不再进行网络请求
NSURLSessionDownloadTask *downloadTask = [self.urlSession downloadTaskWithRequest:downloadRequest];
self.downloadProgressCallback = progress;
self.downloadSuccessCallback = success;
self.downloadFailureCallback = failure;
[downloadTask resume];
self.downloadTask = downloadTask;
return downloadTask;
}
#pragma mark - 接收到服务器返回的数据
/*
bytesWritten: 当前这一次写入的数据大小
totalBytesWritten: 已经写入到本地文件的总大小
totalBytesExpectedToWrite : 被下载文件的总大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (self.downloadTask == downloadTask) {
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat progress = (float) totalBytesWritten / totalBytesExpectedToWrite;
if (self.downloadProgressCallback) {
self.downloadProgressCallback(progress);
}
[self debugLog:NSStringFormat(@"下载文件进度: %.2f",progress)];
});
}
}
#pragma mark - 文件下载完成(NSURLSession内部已经完成了边接收数据边写入沙盒的操作,移动文件到想要的位置即可)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//服务器端的文件名作为当前文件名
NSString *file = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
NSFileManager *fileManager = [NSFileManager defaultManager];
//将文件移动到新的文件路径下
[fileManager moveItemAtPath:location.path toPath:file error:nil];
if (self.downloadSuccessCallback) {
self.downloadSuccessCallback(file);
[self debugLog:NSStringFormat(@"文件下载任务完成,保存路径: %@",file)];
}
}
#pragma mark - 断点续传
- (void)pauseDownloadTaskResumeData:(void (^)(NSData * _Nullable resumeData))completionHandler {
if (!self.downloadTask) { return; }
__weak typeof(self)weakSelf = self;
[self.downloadTask cancelByProducingResumeData:^(NSData *data) {
if (completionHandler) {
completionHandler(data);
[self debugLog:@"当前下载任务被暂停!"];
}
weakSelf.downloadTask = nil;
}];
}
#pragma mark - 恢复下载任务
- (void)resumeDownloadTaskWithResumeData:(NSData *)data {
self.downloadTask = [self.urlSession downloadTaskWithResumeData:data];
[self.downloadTask resume];
data = nil;
[self debugLog:@"继续开始下载任务!"];
}
调试打印
#ifdef DEBUG
@implementation NSArray (UIMSDKNetworkRequestHelper)
- (NSString *)descriptionWithLocale:(id)locale {
NSMutableString *strM = [NSMutableString stringWithString:@"(\n"];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[strM appendFormat:@"\t%@,\n", obj];
}];
[strM appendString:@")"];
return strM;
}
@end
@implementation NSDictionary (UIMSDKNetworkRequestHelper)
- (NSString *)descriptionWithLocale:(id)locale {
NSMutableString *strM = [NSMutableString stringWithString:@"{\n"];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[strM appendFormat:@"\t%@ = %@;\n", key, obj];
}];
[strM appendString:@"}\n"];
return strM;
}
@end
#endif