iOS - NSURLSession 網絡請求


前言

	NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0) @interface NSURLSession : NSObject
	@available(iOS 7.0, *) public class NSURLSession : NSObject

1、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 身份驗證失敗

2、NSURLSession 的設置

  • Objective-C

    • 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"];
      
    • 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};
        
    • 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]];
      
    • 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];
      
    • Task 的設置

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

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

      	#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]];
      

3、NSURLSession 異步 GET 數據請求

  • Objective-C

    • 使用 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];
      
    • 使用 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);
          	}	
      	}
      
    • 使用 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];
      
    • 使用 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);
          	}
      	}
      

4、NSURLSession 異步 POST 數據請求

  • Objective-C

    • 使用 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];
      
    • 使用 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 {
      
      	}
      

5、NSURLSession 文件下載

  • Objective-C

    • 使用 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];
      
    • 使用 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 {
      
      	}
      
    • 使用 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];
      
    • 使用 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 {
      
      	}
      
    • 斷點續傳下載方式

      	// 開始下載
      
          	_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];
          		}
      		}
      
    • 后台下載方式

      	// 配置為后台下載方式
      	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];
      	}
      

6、NSURLSession 文件上傳

  • Objective-C

    • 使用 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];
      
    • 使用 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 {
          	
      	}
      
    • 單文件上傳封裝,使用 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];
      
    • 多文件上傳封裝,使用 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];
      
    • 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];
      
    • 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 {
          	
      		}
      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM