最近搞IM項目佩微,需要盡量減少三方庫的使用残吩,抽時間寫了一個NSURLSession的工具類,目前支持GET, POST捧杉, 單張或多張圖片上傳陕见,單文件上傳與下載。
序言
于2013年的WWDC上發(fā)布味抖,相對NSURLConnection的優(yōu)點:
◎ 支持 http2.0 協(xié)議评甜,
◎ 在處理下載任務(wù)的時候可以直接把數(shù)據(jù)下載到磁盤
支持后臺下載,上傳仔涩,
◎ 同一個 session 發(fā)送多個請求忍坷,只需要建立一次連接(復(fù)用了TCP),
◎ 提供了全局的 session 并且可以統(tǒng)一配置熔脂,使用更加方便佩研,
◎ 下載的時候是多線程異步處理,效率更高霞揉,
主要使用的類:
NSURLSessionTask為抽象類旬薯,使用其子類
1, GET,POST -> NSURLSessionDataTask
2, 上傳 -> NSURLSessionUploadTask
3, 下載 -> NSURLSessionDownloadTask
GET适秩,POST
上代碼绊序,GET,POST請求比較簡單硕舆,我是采用構(gòu)建請求體方法,這樣可以細(xì)化設(shè)置請求的各項參數(shù)骤公,例如超時時間抚官,需要注意當(dāng)url含有中文字符時需要進行編碼,否則轉(zhuǎn)換成的NSURL為nil淋样。
#pragma mark - 構(gòu)建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;
}
文件上傳
要實現(xiàn)POST上傳文件,蘋果沒有做任何封裝耗式,需要安照 W3C 指定的標(biāo)準(zhǔn)格式拼接表單,多文件上傳和單文件上傳的基本思路是一樣的,唯一的區(qū)別在于對請求體的封裝,下面列舉兩種方式:
1胁住,多文件的請求體部分格式1
{
// 第一個文件參數(shù)的上邊界
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=美女\r\n
Content-Type:image/jpeg\r\n\r\n
上傳文件的二進制數(shù)據(jù)部分
// 第一個文件參數(shù)的下邊界
\r\n--boundary--
// 第二個文件參數(shù)的上邊界
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n
Content-Type:text/plain\r\n\r\n
上傳文件的二進制數(shù)據(jù)部分
// 第二個文件參數(shù)的下邊界
\r\n--boundary--
}
2趁猴,多文件上傳的請求體格式2
{
// 上邊界
// 第一個文件參數(shù)
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=美女\r\n
Content-Type:image/jpeg\r\n\r\n
上傳文件的二進制數(shù)據(jù)部分
// 第二個文件參數(shù)
\r\n--boundary\r\n
Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n
Content-Type:text/plain\r\n\r\n
上傳文件的二進制數(shù)據(jù)部分
// 下邊界
\r\n--boundary--
}
}
3,多文件 + 普通文本上傳
{
* 有些服務(wù)器可以在上傳文件的同時,提交一些文本內(nèi)容給服務(wù)器
* 典型應(yīng)用:
<1>新浪微博: 上傳圖片的同時,發(fā)送一條微博信息!
<2>購物評論: 購買商品之后發(fā)表評論的時候圖片+評論內(nèi)容!
多文件上傳的數(shù)據(jù)格式3
{
Content-Type: multipart/form-data; boundary=boundary
// ------ 以下內(nèi)容彪见,是提供給服務(wù)器的二進制數(shù)據(jù)格式
--boundary\r\n
Content-Disposition: form-data; name="userfile[]"; filename="aaa.txt"\r\n
Content-Type: application/octet-stream\r\n\r\n
文件二進制數(shù)據(jù)
\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
文件二進制數(shù)據(jù)
\r\n
--boundary\r\n
// username 是腳本文件接收參數(shù)的名稱
Content-Disposition: form-data; name="username"\r\n\r\n
普通文本二進制數(shù)據(jù)
\r\n
--boundary--
// ------
以上部分儡司,是發(fā)送給服務(wù)器的二進制數(shù)據(jù)的組成格式(示例)
}
static NSString *const kBoundary = @"boundary";
上傳需要的參數(shù)
/**
* 上傳文件
*
* @param urlString 請求地址
* @param parameters 請求參數(shù)
* @param name 文件對應(yīng)服務(wù)器上的字段
* @param filePath 文件本地的沙盒路徑
* @param progress 上傳進度信息
* @param success 請求成功的回調(diào)
* @param failure 請求失敗的回調(diào)
*
* @return 返回的對象可取消請求,調(diào)用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 - 首先構(gòu)建上傳下載的初始請求
- (NSMutableURLRequest *)requestWithUrlString:(NSString *)urlString
cachePolicy:(NSURLRequestCachePolicy)cachePolicy
timeoutInterval:(NSTimeInterval)timeoutInterval {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//設(shè)置忽略緩存與超時時間
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;
// 使用信號量實現(xiàn)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;
}
上傳文件的時候,需要告訴服務(wù)器文件類型(即Content-Type),這時,需要獲取文件的 MIMEType.
使用上方方法即可。如果不想告訴服務(wù)器具體的文件類型,可以使用這個 Content-Type : application/octet-stream(8進制流)
常見的 Content-Type 類型:
{
- 大類型/小類型
- text/plain
- image/jpg
- image/png
- image/gif
- text/html
- application/json
}
#pragma mark - 拼接傳入body內(nèi)的文件部分表單
- (NSData *)bodyDataWithParameters:(NSDictionary *)parameters name:(NSString *)name
filePath:(NSString *)filePath {
//按照W3C格式構(gòu)建上傳數(shù)據(jù)
NSMutableData *bodyData = [self parametersData:parameters];
//獲取本地路徑文件的類型與文件名稱
NSURLResponse *response = [self responseWithLocalFileUrl:filePath];
NSString *contentType = response.MIMEType;
NSString *filename = response.suggestedFilename;;
//設(shè)置服務(wù)器接收名稱與文件名稱
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];
//加入文件的數(shù)據(jù)
[bodyData appendData:fileData];
//下邊界
NSString *lowerBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n",kBoundary];
[bodyData appendData:[lowerBoundary dataUsingEncoding:NSUTF8StringEncoding]];
return bodyData;
}
#pragma mark - 拼接傳入body內(nèi)的參數(shù)部分表單(傳入的類型可能為字符串余指,圖片捕犬,二進制數(shù)據(jù))
- (NSMutableData *)parametersData:(NSDictionary *)parameters {
//按照W3C格式構(gòu)建上傳數(shù)據(jù)
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]];
//拼接主體內(nèi)容
NSString *pair = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
[parametersData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
//根據(jù)字典value類型追加不同數(shù)據(jù)到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)];
}
//換行追加下一條數(shù)據(jù)
[parametersData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
return parametersData;
}
#pragma mark - 執(zhí)行上傳文件操作
- (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]];
//設(shè)置忽略緩存與超時時間
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 - 監(jiān)聽上傳進度(所有的進度監(jiān)聽統(tǒng)一使用一個urlSession管理)
_urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
/**
bytesSent 本次發(fā)送的字節(jié)數(shù)
totalBytesSent 總共發(fā)送的字節(jié)數(shù)
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)];
}
}
上傳單/多張圖片
上傳需要的參數(shù)
/**
* 上傳單/多張圖片
*
* @param urlString 請求地址
* @param parameters 請求參數(shù)
* @param name 圖片對應(yīng)服務(wù)器上的字段
* @param images 圖片數(shù)組
* @param fileNames 圖片文件名數(shù)組, 可以為nil, 數(shù)組內(nèi)的文件名默認(rèn)為當(dāng)前日期時間"yyyyMMddHHmmss"
* @param imageScale 圖片文件壓縮比 范圍 (0.f ~ 1.f)
* @param imageType 圖片文件的類型,例:png、jpg(默認(rèn)類型)....
* @param progress 上傳進度信息
* @param success 請求成功的回調(diào)
* @param failure 請求失敗的回調(diào)
*
* @return 返回的對象可取消請求,調(diào)用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;
執(zhí)行上傳操作酵镜,注意圖片名稱與圖片的匹配碉碉,圖片要進行壓縮處理
- (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(@"圖片名稱與圖片數(shù)組總數(shù)不一致!");
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];
//換行追加下一條數(shù)據(jù)
[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;
}
下載文件
上傳需要的參數(shù)
/**
* 下載文件
*
* @param urlString 請求地址
* @param fileDir 文件存儲目錄
* @param progress 文件下載的進度信息
* @param success 下載成功的回調(diào)(回調(diào)參數(shù)filePath:文件的路徑)
* @param failure 下載失敗的回調(diào)
*
* @return 返回NSURLSessionDownloadTask實例,可用于暫停繼續(xù)淮韭,暫停調(diào)用suspend方法垢粮,開始下載調(diào)用resume方法
*/
- (NSURLSessionDownloadTask *)downloadWithURL:(NSString *)urlString
fileDir:(NSString *)fileDir
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure;
#pragma mark - 執(zhí)行下載操作
注意這里不能直接調(diào)用 self.urlSession downloadTaskWithRequest:downloadRequest completionHandler 需要去掉completionHandler 在代理方法中移動文件與監(jiān)聽進度。
- (NSURLSessionDownloadTask *)downloadWithURL:(NSString *)urlString
fileDir:(NSString *)fileDir
progress:(networkRequestProgress)progress
success:(networkRequestSuccess)success
failure:(networkRequestFailed)failure {
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//NSURLRequestReloadRevalidatingCacheData 驗證本地數(shù)據(jù)與遠(yuǎn)程數(shù)據(jù)是否相同靠粪,如果不同則下載遠(yuǎn)程數(shù)據(jù)蜡吧,否則使用本地數(shù)據(jù)
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:networkRequestTimeoutInterval];
//下載完成后獲取數(shù)據(jù) 此時已經(jīng)自動緩存到本地,下次會直接從本地緩存獲取占键,不再進行網(wǎng)絡(luò)請求
NSURLSessionDownloadTask *downloadTask = [self.urlSession downloadTaskWithRequest:downloadRequest];
self.downloadProgressCallback = progress;
self.downloadSuccessCallback = success;
self.downloadFailureCallback = failure;
[downloadTask resume];
self.downloadTask = downloadTask;
return downloadTask;
}
#pragma mark - 接收到服務(wù)器返回的數(shù)據(jù)
/*
bytesWritten: 當(dāng)前這一次寫入的數(shù)據(jù)大小
totalBytesWritten: 已經(jīng)寫入到本地文件的總大小
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內(nèi)部已經(jīng)完成了邊接收數(shù)據(jù)邊寫入沙盒的操作昔善,移動文件到想要的位置即可)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//服務(wù)器端的文件名作為當(dāng)前文件名
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(@"文件下載任務(wù)完成,保存路徑: %@",file)];
}
}
#pragma mark - 斷點續(xù)傳
- (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:@"當(dāng)前下載任務(wù)被暫停!"];
}
weakSelf.downloadTask = nil;
}];
}
#pragma mark - 恢復(fù)下載任務(wù)
- (void)resumeDownloadTaskWithResumeData:(NSData *)data {
self.downloadTask = [self.urlSession downloadTaskWithResumeData:data];
[self.downloadTask resume];
data = nil;
[self debugLog:@"繼續(xù)開始下載任務(wù)!"];
}
調(diào)試打印
#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