NSURLSession 網絡請求

一、NSURLSession

  • 在 iOS9.0 以後,之前使用的 NSURLConnection 過時,蘋果推薦使用 NSURLSession 來替換 NSURLConnection 完成網路請求相關操做。
  • 1.1 NSURLSession 功能

    • NSURLSession 具備斷點續傳,後臺下載等相關功能。
    • 暫停、中止、重啓網絡任務,再也不須要 NSOperation 封裝。
    • 請求可使用一樣的配置容器中。
    • 不一樣的 session 可使用不一樣的私有存儲。
    • block 和委託能夠同時起做用。
    • 能夠直接從文件系統上傳下載。
    • NSURLSession 的使用很是簡單,先根據會話對象建立一個請求 Task,而後執行該 Task 便可。
    • NSURLSessionTask 自己是一個抽象類,在使用的時候,一般是根據具體的需求使用它的幾個子類。關係以下:
    • NSURLSessionDownloadTask <-- NSURLSessionTask --> NSURLSessionDataTask --> NSURLSessionUploadTask
  • 1.2 發送 GET 請求

    • 使用 NSURLSession 發送 GET 請求的方法和 NSURLConnection 相似,整個過程以下:
      • 1)肯定請求路徑(通常由公司的後臺開發人員以接口文檔的方式提供),GET 請求參數直接跟在 URL 後面;
      • 2)建立請求對象(默認包含了請求頭和請求方法【GET】),此步驟能夠省略;
      • 3)建立會話對象(NSURLSession);
      • 4)根據會話對象建立請求任務(NSURLSessionDataTask);
      • 5)執行 Task;
      • 6)當獲得服務器返回的響應後,解析數據(XML|JSON|HTTP)。
  • 1.3 發送 POST 請求

    • 使用 NSURLSession 發送 POST 請求的方法和 NSURLConnection 相似,整個過程以下:
      • 1)肯定請求路徑(通常由公司的後臺開發人員以接口文檔的方式提供);
      • 2)建立可變的請求對象(由於須要修改),此步驟不能夠省略;
      • 3)修改請求方法爲 POST;
      • 4)設置請求體,把參數轉換爲二進制數據並設置請求體;
      • 5)建立會話對象(NSURLSession);
      • 6)根據會話對象建立請求任務(NSURLSessionDataTask);
      • 7)執行 Task;
      • 8)當獲得服務器返回的響應後,解析數據(XML|JSON|HTTP)。
  • 1.4 文件下載請求

    • 文件下載成功後,若是不作任何處理,下載的文件會被自動刪除。
    • 若是顯示比較大的圖片,NSURLSession 能夠利用磁盤緩存直接下載到本地,不會形成內存佔用太大。
    • 通常從網絡上下載文件,zip 壓縮包會比較多。
    • 若是是 zip 文件,下載完成後須要。
      • 下載壓縮包
      • 解壓縮(異步執行)到目標文件夾
      • 刪除壓縮包
      • 下載任務的特色可讓程序員只關心解壓縮的工做。
  • 1.5 文件上傳請求

    • POST:

      • 須要有一個腳本作支持。
      • 有些腳本有上傳文件大小限制,如 PHP 最大爲 2M。
    • PUT:

      • 不須要腳本,直接以文件的方式寫入服務器。
      • 若是文件不存在,就是新增,若是文件存在就是修改。
      • 文件上傳須要身份驗證。
      • status code: 201 新增
      • status code: 204 修改
      • status code: 401 身份驗證失敗

二、NSURLSession 的設置

  • 2.1 URLRequest 的設置

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"]];
    
    // 設置緩存策略
    /*
    // 默認的緩存策略,會在本地緩存
    NSURLRequestUseProtocolCachePolicy = 0,
    
    // 忽略本地緩存數據,永遠都是從服務器獲取數據,不使用緩存,應用場景:股票,彩票
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData
    
    // 首先使用緩存,若是沒有本地緩存,才從原地址下載
    NSURLRequestReturnCacheDataElseLoad = 2,                                       
    
    // 使用本地緩存,從不下載,若是本地沒有緩存,則請求失敗和 "離線" 數據訪問有關,能夠和 Reachability 框架結合使用,
    // 若是用戶聯網,直接使用默認策略。若是沒有聯網,可使用返回緩存策略,鄭重提示:要把用戶拉到網絡上來。
    NSURLRequestReturnCacheDataDontLoad = 3,
    
    // 無視任何緩存策略,不管是本地的仍是遠程的,老是從原地址從新下載
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,      // Unimplemented
    
    // 若是本地緩存是有效的則不下載,其餘任何狀況都從原地址從新下載
    NSURLRequestReloadRevalidatingCacheData = 5,                // Unimplemented
    
    緩存的數據保存在沙盒路徑下 Caches 文件夾中的 SQLite 數據庫中。
    */
    urlRequest.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    // 設置超時時間
    urlRequest.timeoutInterval = 120;
    
    // 設置請求模式
    /*
    默認是 GET
    */
    urlRequest.HTTPMethod = @"POST";
    
    // 設置請求體
    urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
    // 設置請求頭
    /*
    告訴服務器客戶端類型,只能寫英文,User-Agent 是固定的 key
    */
    [urlRequest setValue:@"iPhone 6s Plus" forHTTPHeaderField:@"User-Agent"];
  • 2.2 URLSessionConfiguration 的設置

    • 在開發一款應用程序的時候,一般只會訪問一臺服務器,若是全部的設置在 session 中統一設置一次,後續的網絡訪問方法,會很是簡單,一次設置,全局有效。
    • 在 URLSession 中,會使用 config 替代不少原有 request 中的附加設置。config 用於設置全局的網絡會話屬性,
    • 包括:瀏覽器類型,Content-Type,身份驗證,Cookie,超時時長,緩存策略,
    • 主機最大鏈接數...。NSURLSessionConfiguration 擁有 20 個屬性。熟練掌握這些屬性的用處,將使應用程序充分利用其網絡環境。
    • 經常使用屬性:
    HTTPAdditionalHeaders           HTTP 請求頭,告訴服務器有關客戶端的附加信息,這對於跨會話共享信息,
    如內容類型,語言,用戶代理,身份認證,是頗有用的。
    Accept                      告訴服務器客戶端可接收的數據類型,如:@"application/json" 。
    Accept-Language             告訴服務器客戶端使用的語言類型,如:@"en" 。
    Authorization               驗證身份信息,如:authString 。
    User-Agent                  告訴服務器客戶端類型,如:@"iPhone AppleWebKit" 。
    range                       用於斷點續傳,如:bytes=10- 。
    
    networkServiceType              網絡服務類型,對標準的網絡流量,網絡電話,語音,視頻,以及由一個後臺進程使用的流量
    進行了區分。大多數應用程序都不須要設置這個。
    NSURLNetworkServiceTypeDefault          默認
    NSURLNetworkServiceTypeVoIP             VoIP
    NSURLNetworkServiceTypeVideo            視頻
    NSURLNetworkServiceTypeBackground       後臺
    NSURLNetworkServiceTypeVoice            語音
    
    allowsCellularAccess            容許蜂窩訪問,和 discretionary 自行決定,被用於節省經過蜂窩鏈接的帶寬。
    建議在使用後臺傳輸的時候,使用 discretionary 屬性,而不是 allowsCellularAccess 
    屬性,由於它會把 WiFi 和電源可用性考慮在內。
    
    timeoutIntervalForRequest       超時時長,許多開發人員試圖使用 timeoutInterval 去限制發送請求的總時間,但這誤會了
    timeoutIntervalForRequest 的意思:報文之間的時間。
    timeoutIntervalForResource      整個資源請求時長,實際上提供了總體超時的特性,這應該只用於後臺傳輸,而不是用戶實際上
    可能想要等待的任何東西。
    
    HTTPMaximumConnectionsPerHost   對於一個 host 的最大併發鏈接數,iOS 默認數值是 4,MAC 下的默認數值是 6,從某種程度上,
    替代了 NSOpeartionQueue 的最大併發線程數。是 Foundation 框架中 URL 加載系統的一個新
    的配置選項。它曾經被用於 NSURLConnection 管理私人鏈接池。如今有了 NSURLSession,開發
    者能夠在須要時限制鏈接到特定主機的數量。平常開發中,幾乎不用去管 session 的最大併發數。
    
    HTTPShouldUsePipelining         也出如今 NSMutableURLRequest,它能夠被用於開啓 HTTP 管道,這能夠顯着下降請求的加載時
    間,可是因爲沒有被服務器普遍支持,默認是禁用的。
    
    sessionSendsLaunchEvents        是另外一個新的屬性,該屬性指定該會話是否應該從後臺啓動。
    
    connectionProxyDictionary       指定了會話鏈接中的代理服務器。一樣地,大多數面向消費者的應用程序都不須要代理,因此基本上不
    須要配置這個屬性。關於鏈接代理的更多信息能夠在 CFProxySupport Reference 找到。
    
    Cookie Policies
    HTTPCookieStorage           被會話使用的 cookie 存儲。默認狀況下,NSHTTPCookieShorage 的 sharedHTTPCookieStorage 
    會被使用,這與 NSURLConnection 是相同的。
    HTTPCookieAcceptPolicy      決定了該會話應該接受從服務器發出的 cookie 的條件。
    HTTPShouldSetCookies        指定了請求是否應該使用會話 HTTPCookieStorage 的 cookie。
    
    Security Policies
    URLCredentialStorage        會話使用的證書存儲。默認狀況下,NSURLCredentialStorage 的sharedCredentialStorage 會被
    使用,這與 NSURLConnection 是相同的。
    
    TLSMaximumSupportedProtocol     肯定是否支持 SSLProtocol 版本的會話。
    TLSMinimumSupportedProtocol     肯定是否支持 SSLProtocol 版本的會話。
    
    Caching Policies
    URLCache                    會話使用的緩存。默認狀況下,NSURLCache 的sharedURLCache 會被使用,這與 NSURLConnection 
    是相同的。
    requestCachePolicy          緩存策略,指定一個請求的緩存響應應該在何時返回。這至關於 NSURLRequest 的 cachePolicy 
    方法。
    
    Custom Protocols
    protocolClasses             註冊 NSURLProtocol 類的特定會話數組。
    
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    // 設置同時鏈接到一臺服務器的最大鏈接數
    
    configuration.HTTPMaximumConnectionsPerHost = 4;
    
    // 設置受權信息,WebDav 的身份驗證
    
    NSString *username = @"admin";
    NSString *password = @"adminpasswd";
    
    NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", username, password];
    NSData   *userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
    NSString *authString = [NSString stringWithFormat:@"Basic: %@", base64EncodedCredential];
    
    // 設置客戶端類型
    
    NSString *userAgentString = @"iPhone AppleWebKit";
    
    // 設置請求頭
    
    configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                            @"Accept-Language": @"en",
                                            @"Authorization": authString,
                                            @"User-Agent": userAgentString};
  • 2.2 URLSession 建立方式

    Important
    The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. 
    If you do not invalidate the session by calling the invalidateAndCancel or resetWithCompletionHandler: method, 
    your app leaks memory.
    
    一旦指定了 session 的代理,session 會對代理進行強引用,若是不主動取消 session,會形成內存泄漏。
    
    釋放強引用的辦法:
    
    1> 網絡操做完成:
    
    取消 session 標記:
    
    session 完成而且無效,已經被取消的會話,沒法再次使用。
    
    __weak typeof(self) weakSelf = self;
    [weakSelf.session finishTasksAndInvalidate];
    
    釋放 session:
    
    __weak typeof(self) weakSelf = self;
    weakSelf.session = nil;
    
    優勢:可以保證下載任務的正常完成。
    壞處:每一次網絡訪問結束後,都要銷燬 session,會形成 session 的重複建立和銷燬。
    
    2> 視圖控制器銷燬以前,將 session 釋放掉:
    
    viewWillDisappear 方法中,將 session 銷燬
    
    [self.session invalidateAndCancel];
    self.session = nil;
    
    好處:只會在視圖控制器被銷燬以前,纔會釋放 session,避免重複的建立和銷燬。
    缺點:session 被取消後,下載任務一樣會被取消(有些版本的 Xcode)。
    
    3> 關於網絡訪問,一般都是創建一個網路訪問的單例:
    
    若是單例的工具類,自己就是 session 的代理,單例會隨着引用程序被銷燬,纔會被釋放。就不須要考慮 session 的釋放問題。
    
    // 共享會話方式
    
    /*
    爲了方便程序員使用,蘋果提供了一個全局 session,全局 session 的回調是異步的,全部的任務都是由 session 發起的。要跟進下
    載進度,不能使用全局 session。
    
    該會話使用全局的 Cache,Cookie 和證書。
    */
    
    NSURLSession *urlSession1 = [NSURLSession sharedSession];
    
    // 配置會話方式
    
    /*
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
    
    configuration:
    
    + (NSURLSessionConfiguration *)defaultSessionConfiguration;
    + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
    
    NS_AVAILABLE(10_10, 8_0)
    + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;
    
    默認會話模式(default):工做模式相似於原來的 NSURLConnection,使用的是基於磁盤緩存的持久化策略,使用用戶鑰匙串中保
    存的證書進行認證受權。
    
    瞬時會話模式(ephemeral):該模式不使用磁盤保存任何數據。全部和會話相關的緩存,證書,cookies 等都被保存在 RAM 中,
    所以當程序使會話無效,這些緩存的數據就會被自動清空。這對於實現像 "祕密瀏覽" 功能的功能來講,是很理想的。
    
    後臺會話模式(background):該模式在後臺完成上傳和下載,後臺會話不一樣於常規的普通的會話,它甚至能夠在應用程序掛起,退
    出,崩潰的狀況下運行上傳和下載任務。初始化時指定的標識符,被用於向任何可能在進程外恢復後臺傳輸的守護進程提供上下文。想要查
    看更多關於後臺會話的信息,能夠查看WWDC Session 204: 「What’s New with Multitasking」。
    */
    
    NSURLSession *urlSession2 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    
    // 配置會話協議方式
    
    /*
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration 
    delegate:(nullable id <NSURLSessionDelegate>)delegate 
    delegateQueue:(nullable NSOperationQueue *)queue;
    
    queue:
    
    注意:下載自己的線程 "只有一條",代理回調能夠在 "多個線程" 回調,指定代理執行的隊列,不會影響到下載自己的執行。
    
    如何選擇隊列:網絡訪問結束後,若是不須要作複雜的操做,能夠指定主隊列,這樣不用考慮線程間通信
    
    主隊列回調:
    
    [NSOperationQueue mainQueue]
    
    代理方法在主線程中調用。
    
    下載自己是異步執行的,這一點和 NSURLConnection 同樣。
    NSURLSession 即便在主線程回調也不會形成阻塞。
    
    異步回調:
    
    [[NSOperationQueue alloc] init]
    nil
    
    代理方法在子線程中調用。
    
    二三兩種方式能夠建立一個新會話並定製其會話類型。該方式中指定了 session 的委託和委託所處的隊列。當再也不須要鏈接時,能夠調用
    Session 的 invalidateAndCancel 直接關閉,或者調用 finishTasksAndInvalidate 等待當前 Task 結束後關閉。這時 Delegate 
    會收到 URLSession:didBecomeInvalidWithError: 這個事件。Delegate 收到這個事件以後會被解引用。
    */
    
    NSURLSession *urlSession3 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  • 2.3 Task 建立方式

    // 數據請求 NSURLSessionDataTask (GET/POST)
    
    // 數據請求 request block 方式
    
    NSURLSessionDataTask *urlSessionDataTask1 = [urlSession1 dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // block 在子線程中執行
    }];
    
    // 數據請求 request 協議 方式
    
    // 遵照協議 <NSURLSessionDataDelegate>
    NSURLSessionDataTask *urlSessionDataTask2 = [urlSession3 dataTaskWithRequest:urlRequest];
    
    // 數據請求 url block 方式
    /*
    1)該方法內部會自動將請求路徑包裝成一個請求對象,該請求對象默認包含了請求頭信息和請求方法(GET)
    2)若是要發送的是 POST 請求,則不能使用該方法。
    */
    
    NSURLSessionDataTask *urlSessionDataTask3 = [urlSession1 dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // block 在子線程中執行
    }];
    
    // 數據請求 url 協議 方式
    /*
    1)該方法內部會自動將請求路徑包裝成一個請求對象,該請求對象默認包含了請求頭信息和請求方法(GET)
    2)若是要發送的是 POST 請求,則不能使用該方法。
    */
    
    // 遵照協議 <NSURLSessionDataDelegate>
    NSURLSessionDataTask *urlSessionDataTask4 = [urlSession3 dataTaskWithURL:url];
    
    // 文件下載 NSURLSessionDownloadTask
    
    // 文件下載 request block 方式
    
    NSURLSessionDownloadTask *urlSessionDownloadTask1 = [urlSession1 downloadTaskWithRequest:urlRequest completionHandler:^(NSURL * _Nullable location,NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    // block 在子線程中執行
    // location 是下載的文件臨時存儲路徑,下載完成後會被自動刪除
    }];
    
    // 文件下載 request 協議 方式
    
    // 遵照協議 <NSURLSessionDownloadDelegate>
    NSURLSessionDownloadTask *urlSessionDownloadTask2 = [urlSession3 downloadTaskWithRequest:urlRequest];
    
    // 文件下載 url block 方式
    
    NSURLSessionDownloadTask *urlSessionDownloadTask3 = [urlSession1 downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    // block 在子線程中執行
    // location 是下載的文件臨時存儲路徑,下載完成後會被自動刪除
    }];
    
    // 文件下載 url 協議 方式
    
    // 遵照協議 <NSURLSessionDownloadDelegate>
    NSURLSessionDownloadTask *urlSessionDownloadTask4 = [urlSession3 downloadTaskWithURL:url];
    
    // 文件下載 resumeData block 方式
    
    NSData *resumeData = nil;
    
    NSURLSessionDownloadTask *urlSessionDownloadTask5 = [urlSession1 downloadTaskWithResumeData:resumeData completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // block 在子線程中執行
    // 斷點續傳,resumeData 爲以前已經下載的數據
    // location 是下載的文件臨時存儲路徑,下載完成後會被自動刪除
    }];
    
    // 文件下載 resumeData 協議 方式
    
    // 遵照協議 <NSURLSessionDownloadDelegate>
    NSURLSessionDownloadTask *urlSessionDownloadTask6 = [urlSession3 downloadTaskWithResumeData:resumeData];
    
    // 文件上傳 NSURLSessionUploadTask
    
    // 文件上傳 fromFile block 方式
    
    NSURL *uploadFileUrl = nil;
    
    NSURLSessionUploadTask *urlSessionUploadTask1 = [urlSession1 uploadTaskWithRequest:urlRequest fromFile:uploadFileUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // block 在子線程中執行
    }];
    
    // 文件上傳 fromFile 協議 方式
    
    // 遵照協議 <NSURLSessionDataDelegate>
    NSURLSessionUploadTask *urlSessionUploadTask2 = [urlSession3 uploadTaskWithRequest:urlRequest fromFile:uploadFileUrl];
    
    // 文件上傳 fromData block 方式
    
    NSData *uploadFileData = nil;
    
    NSURLSessionUploadTask *urlSessionUploadTask3 = [urlSession1 uploadTaskWithRequest:urlRequest fromData:uploadFileData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // block 在子線程中執行
    }];
    
    // 文件上傳 fromData 協議 方式
    
    // 遵照協議 <NSURLSessionDataDelegate>
    NSURLSessionUploadTask *urlSessionUploadTask4 = [urlSession3 uploadTaskWithRequest:urlRequest fromData:uploadFileData];
    
    // 文件上傳 Streamed Request 方式
    
    NSURLSessionUploadTask *urlSessionUploadTask5 = [urlSession1 uploadTaskWithStreamedRequest:urlRequest];
    • 2.4 Task 的設置

    // 開始 Task 任務
    [urlSessionDownloadTask1 resume];
    
    // 暫停 Task 任務
    [urlSessionDownloadTask1 suspend];
    
    // 取消 Task 任務
    
    // 徹底取消,下次下載又從 0.0% 開始
    [urlSessionDownloadTask1 cancel];
    
    // 可恢復性取消,下次下載可從 保存的 resumeData 處開始
    [urlSessionDownloadTask1 cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    
    }];
    • 2.5 文件下載設置

    // location 是下載的文件臨時存儲路徑,下載完成後會被自動刪除。response.suggestedFilename 爲服務器端文件名。
    
    NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:response.suggestedFilename];
    
    // 設置下載的文件存儲路徑
    /*
    處理下載的數據
    */
    [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
  • 2.6 文件上傳設置

    #define boundary @"myBoundary"
    
    // 設置請求頭
    /*
    upload task 不會在請求頭裏添加 content-type (上傳數據類型)字段,@"myBoundary" 爲請求體邊界,參數能夠隨便設置,但需一致
    */
    [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    
    // 設置請求文件參數
    
    NSMutableData *fromBody = [NSMutableData data];
    
    // 參數開始分割線
    /*
    每一個參數開始前都須要加
    */
    [fromBody appendData:[[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 參數
    [fromBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@", @"username", @"jhq"] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 文件開始分割線
    /*
    每一個文件開始前都須要加
    */
    [fromBody appendData:[[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 文件參數名
    /*
    test.png 爲上傳後服務器端文件名稱
    */
    [fromBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"", @"file", @"test.png"] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 文件的類型
    [fromBody appendData:[[NSString stringWithFormat:@"Content-Type: image/png"] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 待上傳文件數據
    /*
    本地待上傳的文件路徑
    */
    [fromBody appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 結束分割線標記
    [fromBody appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

三、NSURLSession 異步 GET 數據請求

  • 3.1 使用 request block 回調方式

    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
    
    // 建立請求對象
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
        // 處理從服務器下載的數據
        if (error == nil && data != nil) {
    
        }
    }];
    
    // 執行任務
    [urlSessionDataTask resume];
  • 3.2 使用 request 協議 方式

    // 遵照協議 <NSURLSessionDataDelegate>
    
    @property(nonatomic, retain)NSMutableData *asyncNetData;
    
    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
    
    // 建立請求對象
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
    delegate:self 
    delegateQueue:[[NSOperationQueue alloc] init]];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest];
    
    // 執行任務
    [urlSessionDataTask resume];
    
    // 協議方法
    
    // 接收到服務器的響應
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                        didReceiveResponse:(NSURLResponse *)response 
                                        completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
            /*
            須要使用 completionHandler 回調告訴系統應該如何處理服務器返回的數據,默認是取消的。
    
            NSURLSessionResponseCancel = 0,             默認的處理方式,取消
            NSURLSessionResponseAllow = 1,              接收服務器返回的數據
            NSURLSessionResponseBecomeDownload = 2,     變成一個下載請求
            NSURLSessionResponseBecomeStream            變成一個流
            */
    
            // 接收服務器返回的數據
            completionHandler(NSURLSessionResponseAllow);
    
            // 異步下載數據源初始化
            self.asyncNetData = [[NSMutableData alloc] init];
    }
    
    // 接收到服務器數據
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
        // 拼接從服務器下載的數據
        [self.asyncNetData appendData:data];
    }
    
    // 服務器的數據加載完畢
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {            
    
        if (error == nil) {
    
            // 處理從服務器下載的數據
            id result = [NSJSONSerialization JSONObjectWithData:self.asyncNetData options:0 error:NULL];
            NSLog(@"異步 GET 網絡請求完成: \n%@", result);
        }   
    }
  • 3.3 使用 url block 回調方式

    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
        // 處理從服務器下載的數據
        if (error == nil && data != nil) {
    
        }
    }];
    // 執行任務
    [urlSessionDataTask resume];
  • 3.4 使用 url 協議 方式

    // 遵照協議 <NSURLSessionDataDelegate>
    
    @property(nonatomic, retain)NSMutableData *asyncNetData;
    
    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                        delegate:self 
                                                        delegateQueue:[[NSOperationQueue alloc] init]];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithURL:url];
    
    // 執行任務
    [urlSessionDataTask resume];
    
    // 協議方法
    
    // 接收到服務器的響應
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
            /*
            須要使用 completionHandler 回調告訴系統應該如何處理服務器返回的數據,默認是取消的。
    
            NSURLSessionResponseCancel = 0,             默認的處理方式,取消
            NSURLSessionResponseAllow = 1,              接收服務器返回的數據
            NSURLSessionResponseBecomeDownload = 2,     變成一個下載請求
            NSURLSessionResponseBecomeStream            變成一個流
            */
    
            // 接收服務器返回的數據
            completionHandler(NSURLSessionResponseAllow);
    
            // 異步下載數據源初始化
            self.asyncNetData = [[NSMutableData alloc] init];
    }
    
    // 接收到服務器數據
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
        // 拼接從服務器下載的數據
        [self.asyncNetData appendData:data];
    }
    
    // 服務器的數據加載完畢
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
        if (error == nil) {
    
        // 處理從服務器下載的數據
        id result = [NSJSONSerialization JSONObjectWithData:self.asyncNetData options:0 error:NULL];
            NSLog(@"異步 GET 網絡請求完成: \n%@", result);
        }
    }

四、NSURLSession 異步 POST 數據請求

  • 4.1 使用 request block 回調方式

    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];
    
    // 建立請求對象
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    
    // 設置請求方式,默認爲 GET 請求
    urlRequest.HTTPMethod = @"POST";
    
    // 設置請求體(請求參數)
    urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }];
    
    // 執行任務
    [urlSessionDataTask resume];
  • 4.2 使用 request 協議 方式

    // 遵照協議 <NSURLSessionDataDelegate>
    
    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];
    
    // 建立請求對象
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    
    // 設置請求方式,默認爲 GET 請求
    urlRequest.HTTPMethod = @"POST";
    
    // 設置請求體(請求參數)
    urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    // 發送請求
    NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest];
    
    // 執行任務
    [urlSessionDataTask resume];
    
    // 協議方法
    
    // 接收到服務器的響應
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
        /*
        須要使用 completionHandler 回調告訴系統應該如何處理服務器返回的數據,默認是取消的。
    
        NSURLSessionResponseCancel = 0,             默認的處理方式,取消
        NSURLSessionResponseAllow = 1,              接收服務器返回的數據
        NSURLSessionResponseBecomeDownload = 2,     變成一個下載請求
        NSURLSessionResponseBecomeStream            變成一個流
        */
    
        // 接收服務器返回的數據
        completionHandler(NSURLSessionResponseAllow);
    }
    
    // 接收到服務器數據
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    }
    
    // 服務器的數據加載完畢
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    }

五、NSURLSession 文件下載

  • 5.1 使用 request block 回調方式

    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
    
    // 建立請求對象                      
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithRequest:urlRequest completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
        if (error == nil) {
    
            // 設置下載的文件存儲路徑
            NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:response.suggestedFilename];
    
            // 處理下載的數據
            [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
        }
    }];
    
    // 執行任務
    [urlSessionDownloadTask resume];
  • 5.2 使用 request 協議 方式

    // 遵照協議 <NSURLSessionDownloadDelegate>
    
    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    
    // 建立請求對象
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    // 發送請求
    NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithRequest:urlRequest];
    
    // 執行任務
    [urlSessionDownloadTask resume];
    
    // 協議方法
    
    // 監聽下載進度,每當寫入數據到臨時文件時,就會調用一次這個方法
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
        // 此次寫入多少
        NSLog(@"bytesWritten: %lli", bytesWritten);
    
        // 已經寫入的大小
        NSLog(@"totalBytesWritten: %lli", totalBytesWritten);
    
        // 總大小
        NSLog(@"totalBytesExpectedToWrite: %lli", totalBytesExpectedToWrite);
    
        float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    
        dispatch_async(dispatch_get_main_queue(), ^{
    
            // 設置下載進度條
            self.progressView.progress = progress;
        });
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    
        // 設置下載的文件存儲路徑
        NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
        // 處理下載的數據
        [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
    }
    
    // 恢復下載任務時調用
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    
    }
    
    // 下載完成或中斷
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    }
  • 5.3 使用 url block 回調方式

    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
        if (error == nil) {
    
            // 設置下載的文件存儲路徑
            NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:response.suggestedFilename];
    
            // 處理下載的數據
            [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
        }
    }];
    
    // 執行任務
    [urlSessionDownloadTask resume];
  • 5.4 使用 url 協議 方式

    // 遵照協議 <NSURLSessionDownloadDelegate>
    
    // 設置請求路徑
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    
    // 建立會話對象
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    // 發送請求
    NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithURL:url];
    
    // 執行任務
    [urlSessionDownloadTask resume];
    
    // 協議方法
    
    // 監聽下載進度,每當寫入數據到臨時文件時,就會調用一次這個方法
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {                    
        // 此次寫入多少
        NSLog(@"bytesWritten: %lli", bytesWritten);
    
        // 已經寫入的大小
        NSLog(@"totalBytesWritten: %lli", totalBytesWritten);
    
        // 總大小
        NSLog(@"totalBytesExpectedToWrite: %lli", totalBytesExpectedToWrite);
    
        float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    
        dispatch_async(dispatch_get_main_queue(), ^{
    
            // 設置下載進度條
            self.progressView.progress = progress;
        }); 
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    
        // 設置下載的文件存儲路徑
        NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
        // 處理下載的數據
        [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
    }
    
    // 恢復下載任務時調用
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    }
    
    // 下載完成或中斷
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    }
  • 5.5 斷點續傳下載方式

    // 開始下載
    
    _downloadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:
    
    _resumeTmpPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"resumeData.tmp"];
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.resumeTmpPath] == NO) {
    
        // 屢次中止下載,下載的臨時文件 會 被自動刪除
        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    
        self.downloadTask = [self.downloadSession downloadTaskWithURL:url];
    
        // 從新開始下載
        [self.downloadTask resume];
    
    } else {
    
        self.resumeData = [NSData dataWithContentsOfFile:self.resumeTmpPath];
    
        // 使用斷點下載須要以前下載的臨時文件存在,才能繼續下載
        self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];
    
        // 斷點開始下載
        [self.downloadTask resume];
    }
    
    // 暫停下載
    
    [self.downloadTask suspend];
    
    // 繼續下載
    
    [self.downloadTask resume];
    
    // 中止下載
    
    // 中止下載。一旦這個 task 被取消了,就沒法再恢復
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    
        if (resumeData) {
    
            self.resumeData = resumeData;
    
            [self.resumeData writeToFile:self.resumeTmpPath atomically:YES];
        }
    
        self.downloadTask = nil;
    }];
    
    // 協議方法
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
        float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
        });
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    
        // 處理下載的數據
        NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
        [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:NULL];
    
        // 刪除斷點下載緩存文件
        [[NSFileManager defaultManager] removeItemAtPath:self.resumeTmpPath error:NULL];
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    
        NSLog(@"恢復下載,已完成:%f%%", (100.0 * fileOffset / expectedTotalBytes));
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
        NSLog(@"didCompleteWithError --- 中斷下載: %@", error.userInfo[NSLocalizedDescriptionKey]);
    
        if (error) {
    
            // 獲取斷點數據
            self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
        }
    
        // 下載的臨時文件不存在時
        if ([error.localizedFailureReason isEqualToString:@"No such file or directory"]) {
    
            // 刪除斷點下載緩存文件,不然繼續斷點下載會報錯
            [[NSFileManager defaultManager] removeItemAtPath:self.resumeTmpPath error:nil];
    
            [self start];
        }
    }
  • 5.6 後臺下載方式

    // 配置爲後臺下載方式
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"myBackgroundID"];     
    _downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]];
    
    [[self.downloadSession downloadTaskWithRequest:request] resume];
    
    // 協議方法
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
        float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
        });
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    
        NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
        stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
        [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:documentsDirPath error:nil];
    }

六、NSURLSession 文件上傳

  • 6.1 使用 formData block 方式

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
    urlRequest.HTTPMethod = @"POST";
    
    #define boundary @"uploadBoundary"
    
    // 設置請求頭
    [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    
    // 設置請求文件參數
    NSMutableData *formData = [NSMutableData data];
    
    // 參數
    [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n",@"username"] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"\r\n%@\r\n", @"qian"] dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 文件
    [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", @"userfile", @"test1.jpg"] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Type: image/jpeg\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]]];
    [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 結束
    [formData appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSURLSession *urlSession = [NSURLSession sharedSession];
    
    NSURLSessionUploadTask *urlSessionUploadTask = [urlSession uploadTaskWithRequest:urlRequest fromData:formData completionHandler:^(NSData * _Nullable data,NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }];
    
    [urlSessionUploadTask resume];
  • 6.2 使用 formData 協議 方式

    // 遵照協議 <NSURLSessionDataDelegate>
    
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
    urlRequest.HTTPMethod = @"POST";
    
    #define boundary @"uploadBoundary"
    
    // 設置請求頭
    [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    
    // 設置請求文件參數
    
    NSMutableData *formData = [NSMutableData data];
    
    // 參數
    [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n",@"username"] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"\r\n%@\r\n", @"qian"] dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 文件
    [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", @"userfile", @"test2.png"]  dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[[NSString stringWithFormat:@"Content-Type: image/png\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]]];
    [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // 結束
    [formData appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    NSURLSessionUploadTask *urlSessionUploadTask = [urlSession uploadTaskWithRequest:urlRequest fromData:formData];
    
    [urlSessionUploadTask resume];
    
    // 協議方法
    
    // 監聽上傳進度
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    
        float progress = 1.0 * totalBytesSent / totalBytesExpectedToSend;
    
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
        });
    }
    
    // 接收到服務器的響應
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
        /*
        須要使用 completionHandler 回調告訴系統應該如何處理服務器返回的數據,默認是取消的。
    
        NSURLSessionResponseCancel = 0,             默認的處理方式,取消
        NSURLSessionResponseAllow = 1,              接收服務器返回的數據
        NSURLSessionResponseBecomeDownload = 2,     變成一個下載請求
        NSURLSessionResponseBecomeStream            變成一個流
        */
    
        completionHandler(NSURLSessionResponseAllow);
    
        // 異步下載數據源初始化
        self.asyncNetData = [[NSMutableData alloc] init];
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
        [self.asyncNetData appendData:data];
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    }
  • 6.3 單文件上傳封裝,使用 formData 方式

    • 文件數據封裝使用到第三方框架 QExtension,具體實現代碼見 GitHub 源碼 QExtension
    #import "NSData+FormData.h"
    
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
    urlRequest.HTTPMethod = @"POST";
    
    NSURL *fileURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]];
    NSData *formData = [NSData q_formDataWithRequest:urlRequest text:@"qian" textName:@"username" fileURL:fileURL name:@"userfile" fileName:nil mimeType:nil];
    
    [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest fromData:formData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }] resume];
  • 6.4 多文件上傳封裝,使用 formData 方式

    • 文件數據封裝使用到第三方框架 QExtension,具體實現代碼見 GitHub 源碼 QExtension
    #import "NSData+FormData.h"
    
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload-m.php"]];
    urlRequest.HTTPMethod = @"POST";
    
    NSURL *fileURL1 = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]];
    NSURL *fileURL2 = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]];
    NSData *formData = [NSData q_formDataWithRequest:urlRequest texts:@[@"qian"] textNames:@[@"username"] fileURLs:@[fileURL1, fileURL2] name:@"userfile[]"fileNames:nil mimeTypes:nil];
    
    [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest fromData:formData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }] resume];
  • 6.5 PUT Block 方式

    // NSString+Base64.m
    
    @implementation NSString (Base64)
    
    - (NSString *)q_base64Encode {
    
        NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];               
    
        return [data base64EncodedStringWithOptions:0];
    }
    
    - (NSString *)q_basic64AuthEncode {
    
        return [@"BASIC " stringByAppendingString:[self q_base64Encode]];
    }
    
    @end
    
    // ViewController.m
    
    // 本地要上傳的文件
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"minion.mp4" withExtension:nil];
    
    // 123.mp4 保存到服務器的文件名
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/uploads/123.mp4"];
    
    // PUT 文件上傳,以文件的方式直接寫入到 WebDav 服務器中
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPMethod = @"PUT";
    
    // 服務器驗證,用戶訪問名和密碼
    [urlRequest setValue:[@"admin:adminpasswd" q_basic64AuthEncode] forHTTPHeaderField:@"Authorization"];
    
    [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest fromFile:fileURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    }] resume];
  • 6.6 PUT 協議 方式

    // NSString+Base64.m
    
    @implementation NSString (Base64)
    
    - (NSString *)q_base64Encode {
    
        NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];               
    
        return [data base64EncodedStringWithOptions:0];
    }
    
    - (NSString *)q_basic64AuthEncode {
        return [@"BASIC " stringByAppendingString:[self q_base64Encode]];
    }
    
    @end
    
    // ViewController.m
    
    // 遵照協議 <NSURLSessionDataDelegate>
    
    // 本地要上傳的文件
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"minion.mp4" withExtension:nil];
    
    // 123.mp4 保存到服務器的文件名
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/uploads/123.mp4"];
    
    // PUT 文件上傳,以文件的方式直接寫入到 WebDav 服務器中
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPMethod = @"PUT";                                                                         
    
    // 服務器驗證,用戶訪問名和密碼
    [urlRequest setValue:[@"admin:adminpasswd" q_basic64AuthEncode] forHTTPHeaderField:@"Authorization"];
    
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    [[urlSession uploadTaskWithRequest:urlRequest fromFile:fileURL] resume];
    
    // 協議方法
    
    // 監聽上傳進度
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent 
    totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    
        float progress = 1.0 * totalBytesSent / totalBytesExpectedToSend;
    
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
        });
    }
    
    // 接收到服務器的響應
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
        /*
        須要使用 completionHandler 回調告訴系統應該如何處理服務器返回的數據,默認是取消的。
    
        NSURLSessionResponseCancel = 0,             默認的處理方式,取消
        NSURLSessionResponseAllow = 1,              接收服務器返回的數據
        NSURLSessionResponseBecomeDownload = 2,     變成一個下載請求
        NSURLSessionResponseBecomeStream            變成一個流
        */
    
        completionHandler(NSURLSessionResponseAllow);
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    }
相關文章
相關標籤/搜索