閱讀目錄
一、整體介紹
NSURLSession在2013年隨著iOS7的發布一起面世,蘋果對它的定位是作為NSURLConnection的替代者,然后逐步將NSURLConnection退出歷史舞臺。現在使用最廣泛的第三方網絡框架:AFNetworking、SDWebImage等等都使用了NSURLSession。作為iOS開發人員,應該緊隨蘋果的步伐,不斷的學習,無論是軟件的更新、系統的更新、API的更新,而不能墨守成規。
Session翻譯為中文意思是會話,我們知道,在七層網絡協議中有物理層->數據鏈路層->網絡層->傳輸層->會話層->表示層->應用層,那我們可以將NSURLSession類理解為會話層,用于管理網絡接口的創建、維護、刪除等等工作,我們要做的工作也只是會話層之后的層即可,底層的工作NSURLSession已經幫我們封裝好了。
另外還有一些Session,比如AVAudioSession用于音視頻訪問,WCSession用于WatchOS通訊,它們都是建立一個會話,并管理會話,封裝一些底層,方便我們使用。舉一反三。
二、使用的一般步驟
其核心就是對網絡任務進行封裝,實現多線程。比如將一個網絡請求交給NSURLSession,最后NSURLSession將訪問結果通過block回調返回,期間自動實現多線程,而且可以通過代理實現監聽(是否成功,當前的進度等等); 大致分為3個步驟:
1NSURL:請求地址,定義一個網絡資源路徑:
NSURL *url = [NSURL URLWithString:@"協議://主機地址/路徑?參數&參數"];
解釋如下:
協議:不同的協議,代表著不同的資源查找方式、資源傳輸方式,比如常用的http,ftp等
主機地址:存放資源的主機的IP地址(域名)
路徑:資源在主機中的具體位置
參數:參數可有可無,也可以多個。如果帶參數的話,用“?”號后面接參數,多個參數的話之間用&隔開
2NSURLRequest:請求,根據前面的NSURL建立一個請求:
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
參數解釋如下:
url:資源路徑
cachePolicy:緩存策略(無論使用哪種緩存策略,都會在本地緩存數據),類型為美劇類型,取值如下:
NSURLRequestUseProtocolCachePolicy = 0 //默認的緩存策略,使用協議的緩存策略
NSURLRequestReloadIgnoringLocalCacheData = 1 //每次都從網絡加載
NSURLRequestReturnCacheDataElseLoad = 2 //返回緩存否則加載,很少使用
NSURLRequestReturnCacheDataDontLoad = 3 //只返回緩存,沒有也不加載,很少使用
timeoutInterval:超時時長,默認60s,這里設置為30s
另外,還可以設置其它一些信息,比如請求頭,請求體等等,如下:
注意,下面的request應為NSMutableURLRequest,即可變類型
//告訴服務器數據為json類型[request setValue:@"application/json"forHTTPHeaderField:@"Content-Type"];//設置請求體(json類型)NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody= jsonData;
3NSURLSession:創建NSURLSession發送請求
為了方便使用,蘋果提供了一個全局的NSURLSession單例,如同NSURLConnection一樣。這樣做的缺陷就是不能監控,如果想要監控每一個請求,則必須通過代理來監聽,我們知道單例是一對多的,而代理是一對一,因此必須自己實例化單獨的Session任務對象(NSURLConnection則很難),來實現單獨監控。
系統一共提供了5種任務類,繼承關系如下圖所示。其中NSURLSessionTask為抽象類,不能實現網絡訪問,NSURLSessionStreanTask(以流的方式進行網絡訪問)使用的比較少.使用的多的是dataTask、downloadTask、uploadTask,即圖中紅色框框圈的部分,基本滿足了網絡訪問的基本需求:獲取數據(通常是JSON、XML等)、文件上傳、文件下載。這三個類都是NSURLSessionTask這個抽象類的子類,相比直接使用NSURLConnection,NSURLSessionTask支持任務的暫停、取消和恢復,并且默認任務運行在其他非主線程中。
根據圖中代理協議的名字不難發現,每一個任務類都有相對應的代理協議,只有NSURLSessionUploadTask沒有對應的代理協議,因為NSURLSessionUploadTask繼承自NSURLSessionDataTask,因此NSURLSessionDataDelegate即為NSURLSessionUploadTask對應的代理協議。
三 舉例
1.NSURLSession請求網絡數據
下面以蘋果提供的全局NSURLSession單例為例,代碼如下:
///向網絡請求數據- (void)NSURLSessionTest {//1.創建url//請求一個網頁NSString *urlString =@"http://www.cnblogs.com/mddblog/p/5215453.html";
//一些特殊字符編碼urlString =[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL*url =[NSURL URLWithString:urlString];//2.創建請求 并:設置緩存策略為每次都從網絡加載 超時時間30秒NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];//3.采用蘋果提供的共享sessionNSURLSession *sharedSession =[NSURLSession sharedSession];//4.由系統直接返回一個dataTask任務NSURLSessionDataTask *dataTask = [sharedSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError *_Nullable error) {//網絡請求完成之后就會執行,NSURLSession自動實現多線程NSLog(@"%@",[NSThread currentThread]);if(data && (error ==nil)) {//網絡訪問成功NSLog(@"data=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}else{//網絡訪問失敗NSLog(@"error=%@",error);
}
}];//5.每一個任務默認都是掛起的,需要調用 resume 方法[dataTask resume];
}
2.NSURLSession文件下載
///文件下載- (void)NSURLSessionDownloadTaskTest {//1.創建urlNSString *urlString = [NSString stringWithFormat:@"http://localhost/周杰倫 - 楓.mp3"];//一些特殊字符編碼urlString =[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL*url =[NSURL URLWithString:urlString];//2.創建請求NSURLRequest *request =[NSURLRequest requestWithURL:url];//3.創建會話,采用蘋果提供全局的共享sessionNSURLSession *sharedSession =[NSURLSession sharedSession];//4.創建任務NSURLSessionDownloadTask *downloadTask = [sharedSession downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError *_Nullable error) {if(error ==nil) {//location:下載任務完成之后,文件存儲的位置,這個路徑默認是在tmp文件夾下!//只會臨時保存,因此需要將其另存NSLog(@"location:%@",location.path);//采用模擬器測試,為了方便將其下載到Mac桌面//NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];NSString *filePath =@"/Users/userName/Desktop/周杰倫 - 楓.mp3";
NSError*fileError;
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:filePath error:&fileError];if(fileError ==nil) {
NSLog(@"file save success");
}else{
NSLog(@"file save error: %@",fileError);
}
}else{
NSLog(@"download error:%@",error);
}
}];//5.開啟任務[downloadTask resume];
}
3.NSURLSession文件上傳
3.1 采用uploadTask任務,以數據流的方式進行上傳
這種方式好處就是大小不受限制,上傳需要服務器端腳本支持,腳本源代碼見本文檔最后的附錄,客戶端示例代碼如下:
///以流的方式上傳,大小理論上不受限制,但應注意時間- (void) NSURLSessionBinaryUploadTaskTest {//1.創建url? 采用Apache本地服務器NSString *urlString =@"http://localhost/upload.php";urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];NSURL *url =[NSURL URLWithString:urlString];//2.創建請求NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url];//文件上傳使用postrequest.HTTPMethod =@"POST";//3.開始上傳? request的body data將被忽略,而由fromData提供[[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:[NSData dataWithContentsOfFile:@"/Users/userName/Desktop/IMG_0359.jpg"]? ? completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError *_Nullable error) {if(error ==nil) {
NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}else{
NSLog(@"upload error:%@",error);
}
}] resume];
}
3.2 采用dataTask任務,拼接表單的方式進行上傳
上傳的關鍵是請求體部分的表單拼接,獲取本地上傳文件的類型(MIME Types),至于具體的網絡上傳則很簡單。 另外拼接表單的方式會有大小限制,即HTML的MAX_FILE_SIZE限制(可以自己設定,一般2MB)。
根據上面的繼承關系圖,我們知道uploadTask是dataTask的子類,也可以使用uploadTask來代替dataTask。在代碼示例中4.2步驟完全可以替換4.1步驟。這時,uploadTaskWithRequest函數的fromData可有可無,文件已在request里面包含。
注意:然而在蘋果官方對uploadTaskWithRequest函數的介紹:request的body data in this request object are ignored,會被忽略,而測試時發現沒有被忽略,且request必須包含HTTPBody,反而fromData被忽略。那么暫時理解為蘋果對uploadTaskWithRequest函數的使用時沒有考慮拼接表單的方式,那么當我們使用拼接表單時,建議不要使用uploadTask,雖然這樣也能成功
服務器端用到的upload.php源代碼見本文最后的附錄
表單拼接格式如下,boundary作為分界線:
--boundary
Content-Disposition:form-data;name=”表單控件名稱”;filename=”上傳文件名稱”
Content-Type:要上傳文件MIME Types
要上傳文件二進制數據;--boundary--
拼接表單示例代碼:
///文件上傳- (void)NSURLSessionUploadTaskTest {//1.創建url? 采用Apache本地服務器NSString *urlString =@"http://localhost/upload/upload.php";
urlString=[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
NSURL*url =[NSURL URLWithString:urlString];//2.創建請求NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url];//文件上傳使用postrequest.HTTPMethod =@"POST";
NSString*contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",@"boundary"];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];//3.拼接表單,大小受MAX_FILE_SIZE限制(2MB)? FilePath:要上傳的本地文件路徑? formName:表單控件名稱,應于服務器一致NSData* data = [self getHttpBodyWithFilePath:@"/Users/userName/Desktop/IMG_0359.jpg"formName:@"file"reName:@"newName.png"];
request.HTTPBody=data;//根據需要是否提供,非必須,如果不提供,session會自動計算[request setValue:[NSString stringWithFormat:@"%lu",data.length] forHTTPHeaderField:@"Content-Length"];//4.1 使用dataTask[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError *_Nullable error) {if(error ==nil) {
NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}else{
NSLog(@"upload error:%@",error);
}
}] resume];#if0//4.2 開始上傳 使用uploadTask? fromData:可有可無,會被忽略[[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:nil? ? completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError *_Nullable error) {if(error ==nil) {
NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}else{
NSLog(@"upload error:%@",error);
}
}] resume];#endif}///filePath:要上傳的文件路徑? formName:表單控件名稱? reName:上傳后文件名- (NSData *)getHttpBodyWithFilePath:(NSString *)filePath formName:(NSString *)formName reName:(NSString *)reName
{
NSMutableData*data =[NSMutableData data];
NSURLResponse*response =[self getLocalFileResponse:filePath];//文件類型:MIMEType? 文件的大小:expectedContentLength? 文件名字:suggestedFilenameNSString *fileType =response.MIMEType;//如果沒有傳入上傳后文件名稱,采用本地文件名!if(reName ==nil) {
reName=response.suggestedFilename;
}//表單拼接NSMutableString *headerStrM =[NSMutableStringstring];
[headerStrM appendFormat:@"--%@\r\n",@"boundary"];//name:表單控件名稱? filename:上傳文件名[headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",formName,reName];
[headerStrM appendFormat:@"Content-Type: %@\r\n\r\n",fileType];
[data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];//文件內容NSData *fileData =[NSData dataWithContentsOfFile:filePath];
[data appendData:fileData];
NSMutableString*footerStrM = [NSMutableString stringWithFormat:@"\r\n--%@--\r\n",@"boundary"];
[data appendData:[footerStrM? dataUsingEncoding:NSUTF8StringEncoding]];//NSLog(@"dataStr=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);returndata;
}///獲取響應,主要是文件類型和文件名- (NSURLResponse *)getLocalFileResponse:(NSString *)urlString
{
urlString=[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];//本地文件請求NSURL *url =[NSURL fileURLWithPath:urlString];
NSURLRequest*request =[NSURLRequest requestWithURL:url];
__block NSURLResponse*localResponse =nil;//使用信號量實現NSURLSession同步請求dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError *_Nullable error) {
localResponse=response;
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);returnlocalResponse;
}
四 NSURLSessionConfiguration
NSURLConnection是全局性的,即它的配置對全局有效,如果有兩個鏈接需要不同的cookies、證書這些公共資源,則NSURLConnection無法滿足要求,這時NSURLSession的優勢則體現出來,NSURLSession可以同過NSURLSessionConfiguration可以設置全局的網絡訪問屬性。
NSURLSessionConfiguration *config =[NSURLSessionConfiguration defaultSessionConfiguration];//delegateQueue:請求完成回調函數和代理函數的運行線程,如果為nil則系統自動創建一個串行隊列,不影響sessionTask的運行線程NSURLSession *session = [NSURLSession sessionWithConfiguration:configdelegate:self delegateQueue:[NSOperationQueue mainQueue]];
三種會話方式:
defaultSessionConfiguration:進程內會話(默認會話),類似 NSURLConnection的標準配置,用硬盤來緩存數據。
ephemeralSessionConfiguration:臨時的進程內會話(內存),不會將cookie、緩存儲存到本地,只會放到內存中,當應用程序退出后數據也會消失,可以用于實現“秘密瀏覽”
backgroundSessionConfiguration:建立后臺會話可以在應用程序掛起,退出,崩潰的情況下運行上傳和下載任務,后臺另起一個線程。另外,系統會根據設備的負載程度決定分配下載的資源,因此有可能會很慢甚至超時失敗。
設置一些網絡屬性:
HTTPAdditionalHeaders:可以設置出站請求的數據頭
configuration.HTTPAdditionalHeaders =@{@"Accept":@"application/json",@"Accept-Language":@"en",@"Authorization": authString,@"User-Agent": userAgentString
};
networkServiceType,設置網絡服務類型
NSURLNetworkServiceTypeDefault 默認
NSURLNetworkServiceTypeVoIP VoIP
NSURLNetworkServiceTypeVideo 視頻
NSURLNetworkServiceTypeBackground 后臺
NSURLNetworkServiceTypeVoice 語音
allowsCellularAccess:允許蜂窩訪問
timeoutIntervalForRequest:請求的超時時長
requestCachePolicy:緩存策略
注意事項:如果是自定義會話并指定了代理,會話會對代理進行強引用,在視圖控制器銷毀之前,需要取消網絡會話,否則會造成內存泄漏
附錄——服務器端文件上傳PHP源代碼
以表單形式上傳,可以獲取文件名等等信息,注意images文件夾的權限應為所有用戶可讀寫:
以文件流的形式上傳文件,文件的名字沒有動態獲取,而是直接命名,注意images文件夾的權限應為所有用戶可讀寫
* $_POST 無法解釋二進制流,需要用到 $GLOBALS['HTTP_RAW_POST_DATA'] 或 php://input
* $GLOBALS['HTTP_RAW_POST_DATA'] 和 php://input 都不能用于 enctype=multipart/form-data
* @param? ? String? $file? 要生成的文件路徑
* @return? boolean*/functionbinary_to_file($file){$content=$GLOBALS['HTTP_RAW_POST_DATA'];//需要php.ini設置if(empty($content)){$content=file_get_contents('php://input');//不需要php.ini設置,內存壓力小}$ret=file_put_contents($file,$content,true);return$ret;
}$file_dir="images/image.png";//固定的文件名,注意設置images文件夾權限為所有用戶可讀寫!!!binary_to_file($file_dir);?>
示例代碼下載
github:https://github.com/mddios/NSURLSessionTest.git
關于NSURLSession還有很多,后續會再貼一些例子 未完待續。。。