純NSURLSession實現(xiàn)網(wǎng)絡(luò)請求

最近搞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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畔乙,一起剝皮案震驚了整個濱河市君仆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牲距,老刑警劉巖返咱,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗅虏,居然都是意外死亡洛姑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門皮服,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楞艾,“玉大人参咙,你說我怎么就攤上這事×蛎校” “怎么了蕴侧?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵墓赴,是天一觀的道長毛嫉。 經(jīng)常有香客問我,道長拒炎,這世上最難降的妖魔是什么裹纳? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任择葡,我火速辦了婚禮,結(jié)果婚禮上剃氧,老公的妹妹穿的比我還像新娘敏储。我一直安慰自己,他們只是感情好朋鞍,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布已添。 她就那樣靜靜地躺著,像睡著了一般滥酥。 火紅的嫁衣襯著肌膚如雪更舞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天坎吻,我揣著相機與錄音缆蝉,去河邊找鬼。 笑死禾怠,一個胖子當(dāng)著我的面吹牛返奉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吗氏,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼芽偏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弦讽?” 一聲冷哼從身側(cè)響起污尉,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎往产,沒想到半個月后被碗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡仿村,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年锐朴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔼囊。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡焚志,死狀恐怖衣迷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酱酬,我是刑警寧澤壶谒,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站膳沽,受9級特大地震影響汗菜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挑社,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一陨界、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滔灶,春花似錦普碎、人聲如沸吼肥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缀皱。三九已至斗这,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啤斗,已是汗流浹背表箭。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钮莲,地道東北人免钻。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像崔拥,于是被迫代替她去往敵國和親极舔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容