纯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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • NSURLSession 使用步骤使用NSURLSession对象创建Task,然后执行Task -(void)g...
    BEYOND黄阅读 894评论 0 0
  • iOS开发系列--网络开发 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可...
    lichengjin阅读 3,629评论 2 7
  • 在苹果彻底弃用NSURLConnection之后自己总结的一个网上的内容,加上自己写的小Demo,很多都是借鉴网络...
    付寒宇阅读 4,249评论 2 13
  • NSURLConnection,在iOS9被宣布弃用,本文不使用NSURLConnection进行网络编程,有兴趣...
    啊左阅读 3,194评论 0 13
  • https://github.com/starainDou 欢迎点星 AFNetWorking 本段代码为总结知识...
    DDY阅读 371评论 0 0