NSURLSession在2013年隨着iOS7的發佈一塊兒面世,蘋果對它的定位是做爲NSURLConnection的替代者,而後逐步將NSURLConnection退出歷史舞臺。如今使用最普遍的第三方網絡框架:AFNetworking、SDWebImage等等都使用了NSURLSession。做爲iOS開發人員,應該緊隨蘋果的步伐,不斷的學習,不管是軟件的更新、系統的更新、API的更新,而不能墨守成規。php
Session翻譯爲中文意思是會話,咱們知道,在七層網絡協議中有物理層->數據鏈路層->網絡層->傳輸層->會話層->表示層->應用層,那咱們能夠將NSURLSession類理解爲會話層,用於管理網絡接口的建立、維護、刪除等等工做,咱們要作的工做也只是會話層以後的層便可,底層的工做NSURLSession已經幫咱們封裝好了。html
另外還有一些Session,好比AVAudioSession用於音視頻訪問,WCSession用於WatchOS通信,它們都是創建一個會話,並管理會話,封裝一些底層,方便咱們使用。觸類旁通。ios
其核心就是對網絡任務進行封裝,實現多線程。好比將一個網絡請求交給NSURLSession,最後NSURLSession將訪問結果經過block回調返回,期間自動實現多線程,並且能夠經過代理實現監聽(是否成功,當前的進度等等); 大體分爲3個步驟:git
NSURL *url = [NSURL URLWithString:@"協議://主機地址/路徑?參數&參數"];
解釋以下: github
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
參數解釋以下: json
另外,還能夠設置其它一些信息,好比請求頭,請求體等等,以下:緩存
注意,下面的request應爲NSMutableURLRequest,便可變類型服務器
// 告訴服務器數據爲json類型 [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; // 設置請求體(json類型) NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil]; request.HTTPBody = jsonData;
下面以蘋果提供的全局NSURLSession單例爲例,代碼以下:cookie
/// 向網絡請求數據 - (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.採用蘋果提供的共享session NSURLSession *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]; }
/// 文件下載 - (void)NSURLSessionDownloadTaskTest { // 1.建立url NSString *urlString = [NSString stringWithFormat:@"http://localhost/周杰倫 - 楓.mp3"]; // 一些特殊字符編碼 urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSURL *url = [NSURL URLWithString:urlString]; // 2.建立請求 NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 3.建立會話,採用蘋果提供全局的共享session NSURLSession *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.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]; // 文件上傳使用post request.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,雖然這樣也能成功
表單拼接格式以下,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]; // 文件上傳使用post request.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]; #if 0 // 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 文件名字:suggestedFilename NSString *fileType = response.MIMEType; // 若是沒有傳入上傳後文件名稱,採用本地文件名! if (reName == nil) { reName = response.suggestedFilename; } // 表單拼接 NSMutableString *headerStrM =[NSMutableString string]; [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]); return data; } /// 獲取響應,主要是文件類型和文件名 - (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); return localResponse; }
NSURLConnection是全局性的,即它的配置對全局有效,若是有兩個連接須要不一樣的cookies、證書這些公共資源,則NSURLConnection沒法知足要求,這時NSURLSession的優點則體現出來,NSURLSession能夠同過NSURLSessionConfiguration能夠設置全局的網絡訪問屬性。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // delegateQueue:請求完成回調函數和代理函數的運行線程,若是爲nil則系統自動建立一個串行隊列,不影響sessionTask的運行線程 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
三種會話方式:
設置一些網絡屬性:
configuration.HTTPAdditionalHeaders = @{ @"Accept": @"application/json", @"Accept-Language": @"en", @"Authorization": authString, @"User-Agent": userAgentString };
注意事項:若是是自定義會話並指定了代理,會話會對代理進行強引用,在視圖控制器銷燬以前,須要取消網絡會話,不然會形成內存泄漏
<?php header("Content-type: application/json; charset=utf-8"); // 配置文件須要上傳到服務器的路徑,須要容許全部用戶有可寫權限,不然沒法上傳! $uploaddir = 'images/'; // file表單名稱,應與客戶端一致 $uploadfile = $uploaddir . basename($_FILES['file']['name']); move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile); echo json_encode($_FILES); ?>
<?php /** 二進制流生成文件 * $_POST 沒法解釋二進制流,須要用到 $GLOBALS['HTTP_RAW_POST_DATA'] 或 php://input * $GLOBALS['HTTP_RAW_POST_DATA'] 和 php://input 都不能用於 enctype=multipart/form-data * @param String $file 要生成的文件路徑 * @return boolean */ function binary_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還有不少,後續會再貼一些例子 未完待續。。。