蘋果在 iOS9 以後已經放棄了NSURLConnection,使用的是 iOS7 以後推出的 NSURLSession。php
NSURLSession 有以下優點:html
1.
NSURLSession
支持 http2.0 協議json
2.在處理下載任務的時候,能夠直接把數據下載到磁盤(經過配置) 3.支持後臺下載|上傳(經過配置) 4.同一個 session 發送多個請求,只需創建一次鏈接(複用了TCP) 5.提供了全局的 session 而且能夠統一配置,使用更加方便 6.下載的時候是多線程異步處理,效率更高緩存
不少人都用過AFNetWorking
和SDWebImage
,其實底層就是封裝了NSURLSession
來請求任務。如今學習一下。安全
AFNetWorking
能夠自動將服務端返回的 JSON 數據識別並解析出來,而使用NSURLSession
則須要本身來完成。服務器
NSURLSession
提供了與各類協議(如HTTP、HTTPS
),以及進行交互的API。NSURLSession
類對象(會話對象)就是用於管理這種交互過程。它是一個高度可配置的容器,經過使用其提供的API,可進行管理控制。markdown
簡單使用,實現基本的網絡請求。cookie
NSURL *url = [NSURL URLWithString:@"http://....."];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// handle response
}];
[task resume];
複製代碼
全部的NSURLSession 網絡請求基本遵循如下幾個步驟:網絡
獲取NSURLSession對象,經過session對象建立 task 任務,執行 task(task 默認是掛起的,經過resume執行)session
NSURLSession
有3種工做模式,不一樣的工做模式解決不一樣的網絡請求場景。
(default)
當你須要保存會話相關的caches、證書、cookies等是就使用default
(ephemeral)
全部和會話相關的caches、證書、cookies等都被緩存在RAM中,當會話銷燬,緩存的數據會被自動清空。(可實現私密瀏覽)
(background)
須要在後臺進行上傳下載就用background
。它會返回一個後臺 session。可在應用程序掛起、退出或崩潰狀況下,運行上傳和下載任務。
上述工做模式由NSURLSessionConfiguration
決定的。其三種建立方式以下:
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;
複製代碼
NSURLSessionConfiguration
提供了不少屬性,從 指定可用網絡,到 cookie,安全性,緩存策略,再到使用自定義協議,啓動事件的設置,以及用於移動設備優化的幾個新屬性,能夠找到幾乎任何你想要進行配置的選項。
關於緩存:
default
模式,能夠將緩存存儲在磁盤上。路徑是沙盒路徑下Library/Caches/bundid/Cache.db
。還有一個常見的設置就是cachePolicy
(緩存策略),它決定要不要從緩存中獲取,好比request.cachePolicy = NSURLRequestUseProtocolCachePolicy
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
//對特定的 URL 請求使用網絡協議中實現的緩存邏輯。這是默認的策略。
NSURLRequestUseProtocolCachePolicy = 0,
//數據須要從原始地址加載。不使用現有緩存。
NSURLRequestReloadIgnoringLocalCacheData = 1,
// 不只忽略本地緩存,同時也忽略代理服務器或其餘中間介質目前已有的、協議容許的緩存。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
//不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼從原始地址加載數據。
NSURLRequestReturnCacheDataElseLoad = 2,
//不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼放棄從原始地址加載數據,請求視爲失敗(即:「離線」模式)。
NSURLRequestReturnCacheDataDontLoad = 3,
//從原始地址確認緩存數據的合法性後,緩存數據就可使用,不然從原始地址加載。
NSURLRequestReloadRevalidatingCacheData = 5,
};
複製代碼
提供了3種建立方法:
一、使用共享的會話,該會話使用全局的Cache,Cookie和證書。
+ (NSURLSession *)sharedSession;
複製代碼
二、經過NSURLSessionConfiguration
配置來建立會話對象。
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
複製代碼
三、經過設置NSURLSessionConfiguration
配置、代理、隊列來建立會話對象。
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;
複製代碼
使用NSURLSession的是爲了完成網絡請求任務。
任務由NSURLSessionTask
這個抽象類封裝,其子類封裝了程序三個最基本的網絡任務:獲取數據(如 JSON 或者 XML),上傳文件,下載文件。
NSURLSessionDataTask
能夠用來處理通常的網絡請求如 GET
等。NSURLSessionUploadTask
用於處理上傳請求。NSURLSessionDownloadTask
主要用於處理下載請求。
下面詳細介紹下三種任務:
任務建立,可經過一個NSURLRequest
或NSURL
建立。
//這兩個方法須要設置代理來接收數據
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
//這兩個方法在completionHandler來接收數據
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
複製代碼
請求任務完成時,它會帶有相關聯的數據,可經過代理,或Block方式處理回調數據。
(1)代理方式
#import "ViewController.h"
@interface ViewController ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self sendRequest];
}
- (void)sendRequest{
//建立請求
NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//設置request的緩存策略(決定該request是否要從緩存中獲取)
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
//建立配置(決定要不要將數據和響應緩存在磁盤)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;
//建立會話
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
//生成任務
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
//建立的task是中止狀態,須要咱們去啓動
[task resume];
}
//1.接收到服務器響應的時候調用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
NSLog(@"接收響應");
//必須告訴系統是否接收服務器返回的數據
//默認是completionHandler(NSURLSessionResponseAllow)
//能夠再這邊經過響應的statusCode來判斷否接收服務器返回的數據
completionHandler(NSURLSessionResponseAllow);
}
//2.接受到服務器返回數據的時候調用,可能被調用屢次
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
NSLog(@"接收到數據");
//通常在這邊進行數據的拼接,在方法3纔將完整數據回調
// NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
//3.請求完成或者是失敗的時候調用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
NSLog(@"請求完成或者是失敗");
//在這邊進行完整數據的解析,回調
}
//4.將要緩存響應的時候調用(必須是默認會話模式,GET請求才能夠)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{
//能夠在這邊更改是否緩存,默認的話是completionHandler(proposedResponse)
//不想緩存的話能夠設置completionHandler(nil)
completionHandler(proposedResponse);
}
@end
複製代碼
(2)Block方式(便捷、經常使用)
NSURL *url = [NSURL URLWithString:@"http://www.connect.com/login"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"username=Tom&pwd=123" dataUsingEncoding:NSUTF8StringEncoding];
//使用全局的會話
NSURLSession *session = [NSURLSession sharedSession];
// 經過request初始化task
NSURLSessionTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
}];
// 啓動
[task resume];
複製代碼
繼承自NSURLSessionDataTask
。UploadTask只不過在Http請求的時候,把數據放到Http Body中。 NSURLSessionUploadTask
經過request
建立,在上傳時指定文件源或數據源。
/=========代理方式===========/
//經過文件url來上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
//經過文件data來上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
//經過文件流來上傳
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
/=========Block方式===========/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
複製代碼
3種建立的區別:
NSData:若是對象已經在內存裏 File:若是對象在磁盤上,這樣作有助於下降內存使用 Stream:經過流對象,能夠不用一次性將全部的流數據加載到內存中 不過使用Stream必定要實現URLSession:task:needNewBodyStream:
,由於Session沒辦法在從新嘗試發送Stream的時候找到數據源。
基本上傳任務的使用: Uploadtask 的建立須要使用一個 request
,另外加上一個要上傳的 NSData
對象 或者是一個本地文件的路徑對應的 NSURL
:
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSData *data = ...;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
fromData:data
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[uploadTask resume];
複製代碼
上傳圖片的例子
- (void)uploadRequest{
//建立請求
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.freeimagehosting.net/upload.php"]];
//若是是上傳文字就是@"application/json"
[request addValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"text/html" forHTTPHeaderField:@"Accept"];
[request setHTTPMethod:@"POST"];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setTimeoutInterval:20];
NSData * imagedata = UIImageJPEGRepresentation([UIImage imageNamed:@"person"],1.0);
//建立配置(決定要不要將數據和響應緩存在磁盤)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//建立會話
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionUploadTask * uploadtask = [session uploadTaskWithRequest:request fromData:imagedata completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//發送完成的回調
}];
[uploadtask resume];
}
//發送數據過程當中會執行(執行屢次)
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
NSLog(@"發送數據中");
//在這邊監聽發送的進度
//progress = totalBytesSent/(float)totalBytesExpectedToSend
}
複製代碼
下載文件能夠實現斷點下載 內部已經實現了數據邊接收、邊寫入沙盒的操做(直接下載到磁盤) 支持BackgroundSession(後臺下載)
Download task
也須要一個 request
或者Url
,不一樣之處在於 completionHandler
這個 block。它是將數據一點點地寫入本地的臨時文件。因此在 completionHandler
裏,須要把文件從一個臨時地址,移動到一個永久的地址保存起來。而Datatask
和 uploadtask
會在任務完成時一次性返回。
NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
completionHandler: ^(NSURL *location, NSURLResponse *response, NSError *error) {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
NSURL *newFileLocation = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
[[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil];
}];
[downloadTask resume];
複製代碼
普通下載過程: (代理方式,需遵照NSURLSessionDelegate,NSURLSessionDownloadDelegate
)
- (void)downloadRequest{
//建立請求
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/image/jpeg"]];
//建立配置(決定要不要將數據和響應緩存在磁盤)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//建立會話
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDownloadTask * downloadtask = [session downloadTaskWithRequest:request];
[downloadtask resume];
}
// 代理回調方法
//1. downloadTask下載過程當中會執行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(@"下載中...");
NSLog(@"寫入數據大小%lld,總寫入數據大小%lld,總指望數據大小%lld",bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
//監聽下載的進度
}
//2.downloadTask下載完成的時候會執行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"下載完成");
//該方法內部已經完成了邊接收數據邊寫沙盒的操做,解決了內存飆升的問題
//對數據進行使用,或者保存(默認存儲到臨時文件夾 tmp 中,須要剪切文件到 cache)
//保存
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
//使用
NSData * data = [NSData dataWithContentsOfURL:location.filePathURL];
UIImage * image = [UIImage imageWithData:data];
UIImageWriteToSavedPhotosAlbum(image, nil,nil,nil);
}
//3.請求完成或者是失敗的時候調用(Session層次的Task完成的事件)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
NSLog(@"請求完成或者是失敗");
}
複製代碼
接下來了解一下,下載的API。除了經過url或request下載外,還能夠經過以前已經下載的數據來建立下載任務(也就是斷點續傳)。一樣地能夠經過completionHandler
指定任務完成後的回調代碼塊。
// 使用這種方式取消下載能夠獲得未來用來恢復的數據,保存起來
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
self.resumeData = resumeData;
}];
// 因爲下載失敗致使的下載中斷會進入此協議方法,也能夠獲得用來恢復的數據
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
// 保存恢復數據
self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}
// 恢復下載時接過保存的恢復數據
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
// 啓動任務
[self.task resume];
複製代碼
斷點續傳的前提是,服務器也支持斷點續傳。