多線程與網絡(七) - NSURLSession - AFN

NSURLSession - AFN

1.NSURLConnection和Runloop

  • 1.1 涉及知識點

(1)兩種爲NSURLConnection設置代理方式的區別數組

//第一種設置方式:
    //經過該方法設置代理,會自動的發送請求
    // [[NSURLConnection alloc]initWithRequest:request delegate:self];

    //第二種設置方式:
    //設置代理,startImmediately爲NO的時候,該方法不會自動發送請求
    NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
    //手動經過代碼的方式來發送請求
    //注意該方法內部會自動的把connect添加到當前線程的RunLoop中在默認模式下執行
    [connect start];

(2)如何控制代理方法在哪一個線程調用安全

//說明:默認狀況下,代理方法會在主線程中進行調用(爲了方便開發者拿到數據後處理一些刷新UI的操做不須要考慮到線程間通訊)
    //設置代理方法的執行隊列
    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];

(3)開子線程發送網絡請求的注意點,適用於自動發送網絡請求模式服務器

```objc
//使用GCD開啓一個子線程來發送網絡請求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//使用非自動發送網絡請求模式,發送請求OK
/
//建立NSURLConnection對象,設置代理,暫不發送
NSURLConnection
connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
//設置代理方法的執行隊列
[connect setDelegateQueue:[[NSOperationQueue alloc]init]];網絡

//調用start發送網絡請求
    [connect start];
    */

    //使用自動發送網絡請求模式,發送請求失敗(須要改造代碼)
    //WHY?
    /*01 網絡請求發送和數據接收是否成功,和一些因素相關,好比客戶端的網速、服務器端的查詢速度等等。
      02 而在子線程中建立的NSURLConnection對象是一個臨時變量,當請求發送完成以後就被釋放了,因此這個時候它的代理方法不會調用用。
      03 爲何使用非自動發送網絡請求模式是OK的。
        由於在該模式中,調用了start來開始發送網絡請求,該方法內部會自動將當前的connect做爲一個Source添加到當前線程所在的Runloop中
        若是當前線程是子線程(即當前線程的runloop並未建立),那麼該方法內部會默認先建立當前線程的Runloop,設置在runloop的默認模式下運行。
        此時runloop會對這個Connect對象進行強引用,保證了代理方法被調用的前提
     */
    NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self];
    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
    //建立當前線程的runloop,並開啓runloop
    [[NSRunLoop currentRunLoop] run];
});

```

2.NSURLSession的基本使用

  • 2.1 涉及知識點

(1)使用步驟session

使用NSURLSession建立task,而後執行task

(2)關於taskapp

a.NSURLSessionTask是一個抽象類,自己不能使用,只能使用它的子類
    b.NSURLSessionDataTask\NSURLSessionUploadTask\NSURLSessionDownloadTask

(3)發送get請求框架

//1.建立NSURLSession對象(能夠獲取單例對象)
    NSURLSession *session = [NSURLSession sharedSession];

    //2.根據NSURLSession對象建立一個Task

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=ss&pwd=ss&type=JSON"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //方法參數說明
    /*
    注意:該block是在子線程中調用的,若是拿到數據以後要作一些UI刷新操做,那麼須要回到主線程刷新
    第一個參數:須要發送的請求對象
    block:當請求結束拿到服務器響應的數據時調用block
    block-NSData:該請求的響應體
    block-NSURLResponse:存放本次請求的響應信息,響應頭,真實類型爲NSHTTPURLResponse
    block-NSErroe:請求錯誤信息
     */
   NSURLSessionDataTask * dataTask =  [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {

        //拿到響應頭信息
        NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;

        //4.解析拿到的響應數據
        NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);
    }];

    //3.執行Task
    //注意:剛建立出來的task默認是掛起狀態的,須要調用該方法來啓動任務(執行任務)
    [dataTask resume];

(4)發送get請求的第二種方式async

//注意:該方法內部默認會把URL對象包裝成一個NSURLRequest對象(默認是GET請求)
    //方法參數說明
    /*
    //第一個參數:發送請求的URL地址
    //block:當請求結束拿到服務器響應的數據時調用block
    //block-NSData:該請求的響應體
    //block-NSURLResponse:存放本次請求的響應信息,響應頭,真實類型爲NSHTTPURLResponse
    //block-NSErroe:請求錯誤信息
     */
- (nullable NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error))completionHandler;

(5)發送POST請求ide

//1.建立NSURLSession對象(能夠獲取單例對象)
    NSURLSession *session = [NSURLSession sharedSession];

    //2.根據NSURLSession對象建立一個Task

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];

    //建立一個請求對象,並這是請求方法爲POST,把參數放在請求體中傳遞
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBody = [@"username=123&pwd=456&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {
        //拿到響應頭信息
        NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;

        //解析拿到的響應數據
        NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);
    }];

    //3.執行Task
    //注意:剛建立出來的task默認是掛起狀態的,須要調用該方法來啓動任務(執行任務)
    [dataTask resume];

3.NSURLSession下載文件-代理

  • 3.1 涉及知識點

(1)建立NSURLSession對象,設置代理(默認配置)oop

//1.建立NSURLSession,並設置代理
    /*
     第一個參數:session對象的全局配置設置,通常使用默認配置就能夠
     第二個參數:誰成爲session對象的代理
     第三個參數:代理方法在哪一個隊列中執行(在哪一個線程中調用),若是是主隊列那麼在主線程中執行,若是是非主隊列,那麼在子線程中執行
     */
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

(2)根據Session對象建立一個NSURLSessionDataTask任務(post和get選擇)

//建立task
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_01.png"];

//注意:若是要發送POST請求,那麼請使用dataTaskWithRequest,設置一些請求頭信息
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];

(3)執行任務(其它方法,如暫停、取消等)

//啓動task
    //[dataTask resume];
    //其它方法,如取消任務,暫停任務等
    //[dataTask cancel];
    //[dataTask suspend];

(4)遵照代理協議,實現代理方法(3個相關的代理方法)

/*
 1.當接收到服務器響應的時候調用
     session:發送請求的session對象
     dataTask:根據NSURLSession建立的task任務
     response:服務器響應信息(響應頭)
     completionHandler:經過該block回調,告訴服務器端是否接收返回的數據
 */
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler

/*
 2.當接收到服務器返回的數據時調用
 該方法可能會被調用屢次
 */
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data

/*
 3.當請求完成以後調用該方法
 不管是請求成功仍是請求失敗都調用該方法,若是請求失敗,那麼error對象有值,不然那麼error對象爲空
 */
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error

(5)當接收到服務器響應的時候,告訴服務器接收數據(調用block)

//默認狀況下,當接收到服務器響應以後,服務器認爲客戶端不須要接收數據,因此後面的代理方法不會調用
    //若是須要繼續接收服務器返回的數據,那麼須要調用block,並傳入對應的策略

    /*
        NSURLSessionResponseCancel = 0, 取消任務
        NSURLSessionResponseAllow = 1,  接收任務
        NSURLSessionResponseBecomeDownload = 2, 轉變成下載
        NSURLSessionResponseBecomeStream NS_ENUM_AVAILABLE(10_11, 9_0) = 3, 轉變成流
    */

    completionHandler(NSURLSessionResponseAllow);

4.NSURLSessionDownloadTask實現大文件下載

  • 4.1 涉及知識點

(1)使用NSURLSession和NSURLSessionDownload能夠很方便的實現文件下載操做

/*
     第一個參數:要下載文件的url路徑
     第二個參數:當接收完服務器返回的數據以後調用該block
     location:下載的文件的保存地址(默認是存儲在沙盒中tmp文件夾下面,隨時會被刪除)
     response:服務器響應信息,響應頭
     error:該請求的錯誤信息
     */
    //說明:downloadTaskWithURL方法已經實現了在下載文件數據的過程當中邊下載文件數據,邊寫入到沙盒文件的操做
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * __nullable location, NSURLResponse * __nullable response, NSError * __nullable error)

(2)downloadTaskWithURL內部默認已經實現了變下載邊寫入操做,因此不用開發人員擔憂內存問題

(3)文件下載後默認保存在tmp文件目錄,須要開發人員手動的剪切到合適的沙盒目錄

(4)缺點:沒有辦法監控下載進度


5.使用NSURLSessionDownloadTask實現大文件下載-監聽下載進度

  • 5.1 涉及知識點

(1)建立NSURLSession並設置代理,經過NSURLSessionDownloadTask並以代理的方式來完成大文件的下載

//1.建立NSULRSession,設置代理
    self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    //2.建立task
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    self.downloadTask = [self.session downloadTaskWithURL:url];

    //3.執行task
    [self.downloadTask resume];

(2)經常使用代理方法的說明

/*
 1.當接收到下載數據的時候調用,能夠在該方法中監聽文件下載的進度
 該方法會被調用屢次
 totalBytesWritten:已經寫入到文件中的數據大小
 totalBytesExpectedToWrite:目前文件的總大小
 bytesWritten:本次下載的文件數據大小
 */
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
/*
 2.恢復下載的時候調用該方法
 fileOffset:恢復以後,要從文件的什麼地方開發下載
 expectedTotalBytes:該文件數據的總大小
 */
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
/*
 3.下載完成以後調用該方法
 */
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location
/*
 4.請求完成以後調用
 若是請求失敗,那麼error有值
 */
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error

(3)實現斷點下載相關代碼

//若是任務,取消了那麼之後就不能恢復了
    //    [self.downloadTask cancel];

    //若是採起這種方式來取消任務,那麼該方法會經過resumeData保存當前文件的下載信息
    //只要有了這份信息,之後就能夠經過這些信息來恢復下載
    [self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) {
        self.resumeData = resumeData;
    }];

    -----------
    //繼續下載
    //首先經過以前保存的resumeData信息,建立一個下載任務
    self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];

     [self.downloadTask resume];

(4)計算當前下載進度

//獲取文件下載進度
    self.progress.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;

(5)侷限性

01 若是用戶點擊暫停以後退出程序,那麼須要把恢復下載的數據寫一份到沙盒,代碼複雜度更
02 若是用戶在下載中途未保存恢復下載數據即退出程序,則不具有可操做性

6.使用NSURLSessionDataTask實現大文件離線斷點下載(完整)

  • 6.1 涉及知識點

(1)關於NSOutputStream的使用

//1. 建立一個輸入流,數據追加到文件的屁股上
    //把數據寫入到指定的文件地址,若是當前文件不存在,則會自動建立
    NSOutputStream *stream = [[NSOutputStream alloc]initWithURL:[NSURL fileURLWithPath:[self fullPath]] append:YES];

    //2. 打開流
    [stream open];

    //3. 寫入流數據
    [stream write:data.bytes maxLength:data.length];

    //4.當不須要的時候應該關閉流
    [stream close];

(2)關於網絡請求請求頭的設置(能夠設置請求下載文件的某一部分)

//1. 設置請求對象
    //1.1 建立請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];

    //1.2 建立可變請求對象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //1.3 拿到當前文件的殘留數據大小
    self.currentContentLength = [self FileSize];

    //1.4 告訴服務器從哪一個地方開始下載文件數據
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentContentLength];
    NSLog(@"%@",range);

    //1.5 設置請求頭
    [request setValue:range forHTTPHeaderField:@"Range"];

(3)NSURLSession對象的釋放

-(void)dealloc
{
    //在最後的時候應該把session釋放,以避免形成內存泄露
    //    NSURLSession設置過代理後,須要在最後(好比控制器銷燬的時候)調用session的invalidateAndCancel或者resetWithCompletionHandler,纔不會有內存泄露
    //    [self.session invalidateAndCancel];
    [self.session resetWithCompletionHandler:^{

        NSLog(@"釋放---");
    }];
}

(4)優化部分

01 關於文件下載進度的實時更新
    02 方法的獨立與抽取

7.NSURLSession實現文件上傳

  • 7.1 涉及知識點

(1)實現文件上傳的方法

/*
     第一個參數:請求對象
     第二個參數:請求體(要上傳的文件數據)
     block回調:
     NSData:響應體
     NSURLResponse:響應頭
     NSError:請求的錯誤信息
     */
    NSURLSessionUploadTask *uploadTask =  [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error)

(2)設置代理,在代理方法中監聽文件上傳進度

/*
 調用該方法上傳文件數據
 若是文件數據很大,那麼該方法會被調用屢次
 參數說明:
     totalBytesSent:已經上傳的文件數據的大小
     totalBytesExpectedToSend:文件的總大小
 */
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    NSLog(@"%.2f",1.0 * totalBytesSent/totalBytesExpectedToSend);
}

(3)關於NSURLSessionConfiguration相關

01 做用:能夠統一配置NSURLSession,如請求超時等
02 建立的方式和使用
//建立配置的三種方式
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);

//統一配置NSURLSession
-(NSURLSession *)session
{
    if (_session == nil) {

        //建立NSURLSessionConfiguration
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

        //設置請求超時爲10秒鐘
        config.timeoutIntervalForRequest = 10;

        //在蜂窩網絡狀況下是否繼續請求(上傳或下載)
        config.allowsCellularAccess = NO;

        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

8.AFN框架基本使用

  • 8.1 AFN內部結構
AFN結構體
    - NSURLConnection
        + AFURLConnectionOperation
        + AFHTTPRequestOperation
        + AFHTTPRequestOperationManager(封裝了經常使用的 HTTP 方法)
            * 屬性
                * baseURL :AFN建議開發者針對 AFHTTPRequestOperationManager 自定義個一個單例子類,設置 baseURL, 全部的網絡訪問,都只使用相對路徑便可
                * requestSerializer :請求數據格式/默認是二進制的 HTTP
                * responseSerializer :響應的數據格式/默認是 JSON 格式
                * operationQueue
                * reachabilityManager :網絡鏈接管理器
            * 方法
                * manager :方便建立管理器的類方法
                * HTTPRequestOperationWithRequest :在訪問服務器時,若是要告訴服務器一些附加信息,都須要在 Request 中設置
                * GET
                * POST

    - NSURLSession
        + AFURLSessionManager
        + AFHTTPSessionManager(封裝了經常使用的 HTTP 方法)
            * GET
            * POST
            * UIKit + AFNetworking 分類
            * NSProgress :利用KVO

    - 半自動的序列化&反序列化的功能
        + AFURLRequestSerialization :請求的數據格式/默認是二進制的
        + AFURLResponseSerialization :響應的數據格式/默認是JSON格式
    - 附加功能
        + 安全策略
            * HTTPS
            * AFSecurityPolicy
        + 網絡檢測
            * 對蘋果的網絡鏈接檢測作了一個封裝
            * AFNetworkReachabilityManager

建議:
能夠學習下AFN對 UIKit 作了一些分類, 對本身能力提高是很是有幫助的
  • 8.2 AFN的基本使用

(1)發送GET請求的兩種方式(POST同)

-(void)get1
{
    //1.建立AFHTTPRequestOperationManager管理者
    //AFHTTPRequestOperationManager內部是基於NSURLConnection實現的
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    //2.發送請求
    /*
     http://120.25.226.186:32812/login?username=ee&pwd=ee&type=JSON
     第一個參數:NSString類型的請求路徑,AFN內部會自動將該路徑包裝爲一個url並建立請求對象
     第二個參數:請求參數,以字典的方式傳遞,AFN內部會判斷當前是POST請求仍是GET請求,以選擇直接拼接仍是轉換爲NSData放到請求體中傳遞
     第三個參數:請求成功以後回調Block
     第四個參數:請求失敗回調Block
     */

    NSDictionary *param = @{
                            @"username":@"123",
                            @"pwd":@"456"
                            };

    //注意:字符串中不能包含空格
    [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {

        NSLog(@"請求成功---%@",responseObject);

    } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
            NSLog(@"失敗---%@",error);
    }];
}

-(void)get2
{
    //1.建立AFHTTPSessionManager管理者
    //AFHTTPSessionManager內部是基於NSURLSession實現的
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

    //2.發送請求
    NSDictionary *param = @{
                            @"username":@"123",
                            @"pwd":@"456"
                            };

    //注意:responseObject:請求成功返回的響應結果(AFN內部已經把響應體轉換爲OC對象,一般是字典或數組)
    [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
            NSLog(@"請求成功---%@",[responseObject class]);

    } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
        NSLog(@"失敗---%@",error);
    }];
}

(2)使用AFN下載文件

-(void)download
{
    //1.建立一個管理者
    AFHTTPSessionManager *manage  = [AFHTTPSessionManager manager];

    //2.下載文件
    /*
     第一個參數:請求對象
     第二個參數:下載進度
     第三個參數:block回調,須要返回一個url地址,用來告訴AFN下載文件的目標地址
         targetPath:AFN內部下載文件存儲的地址,tmp文件夾下
         response:請求的響應頭
         返回值:文件應該剪切到什麼地方
     第四個參數:block回調,當文件下載完成以後調用
        response:響應頭
        filePath:文件存儲在沙盒的地址 == 第三個參數中block的返回值
        error:錯誤信息
     */

    //2.1 建立請求對象
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]];

    //2.2 建立下載進度,並監聽
    NSProgress *progress = nil;

    NSURLSessionDownloadTask *downloadTask = [manage downloadTaskWithRequest:request progress:&progress destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {

        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        //拼接文件全路徑
        NSString *fullpath = [caches stringByAppendingPathComponent:response.suggestedFilename];
        NSURL *filePathUrl = [NSURL fileURLWithPath:fullpath];
        return filePathUrl;

    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error) {

        NSLog(@"文件下載完畢---%@",filePath);
    }];

    //2.3 使用KVO監聽下載進度
    [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil];

    //3.啓動任務
    [downloadTask resume];
}

//獲取並計算當前文件的下載進度
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSProgress *)progress change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"%zd--%zd--%f",progress.completedUnitCount,progress.totalUnitCount,1.0 * progress.completedUnitCount/progress.totalUnitCount);
}
相關文章
相關標籤/搜索