記得我剛作iOS的時候,那時候仍是ASI和AFN共存,甚至ASI使用比例還多點,一轉眼幾年過去,ASI基本已經消失了,AFN基本成了iOS項目的標配。我雖然之前也有看過AFN2.x的源碼,可是對於AFN3.x的源碼一直沒有本身閱讀。接下來我會對AFN3.x學習而且寫博客記錄。得益於NSURLSession
的強大功能,ANF3.0放棄了NSURLConnection
這一部分,讓代碼簡化了不少,可是功能卻更加豐富。我以爲在學習AFN以前,有必要仔細瞭解NSURLSession
和https
相關,否則會有不少地方疑惑不解,具體能夠看個人git倉庫![iOSSourceCodeStudy
](https://github.com/huang30351...。同時我強烈推薦瀏覽一下NSURLSession.h
這個文件。git
咱們首先來看一下一個簡單的NSURLSession
請求代碼:github
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:bigPic]]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; [dataTask resume];
從上面咱們發現,咱們要發送一個網絡請求,須要新建一個NSURLSession
,新建一個NSURLSession
又須要一個NSURLSessionConfiguration
,而且還須要一些代理方法。同時咱們須要一個NSURLSessionDataTask
。因此說,咱們的NSRULSession
網絡請求系統包括一個session、一個configuration、一個Task已經Task附帶的delegate。api
一個NSURLSession
,總共只有一個類,也是最核心的類,他有一個對應的代理NSURLSessionDelegate
。緩存
一個NSURLSessionConfiguration
,總共有三種模式。服務器
一個NSURLSessionTask
。NSURLSessionTask
是抽閒類,對應的代理NSURLSessionTaskDelegate
。咱們具體使用的時候,會使用他的三種子類,並且每一個子類都有對應的delegate。網絡
首先咱們看一下NSRULSession.h
裏面關於NSURLSession
的部分。咱們把它分爲初始化部分、屬性部分、dataTask部分、uploadTask部分、downloadTask部分。也就是說其餘不少類都是圍繞着下面這幾個api衍生的。後面咱們會每一個部分分析。session
//初始化部分 @property (class, readonly, strong) NSURLSession *sharedSession; + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration; + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue; //屬性部分 @property (readonly, retain) NSOperationQueue *delegateQueue; @property (nullable, readonly, retain) id <NSURLSessionDelegate> delegate; @property (readonly, copy) NSURLSessionConfiguration *configuration; //dataTask部分 - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request; - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url; //uploadTask部分 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData; - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request; //downloadTask部分 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request; - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url; - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
咱們都知道,NSRULConnection
除了一套使用代理的API,還有一套對應的使用Block的api。NSURLSession
也不列外。使用這一套api就不用實現代理方法。和delegate同樣,Block也有dataTask系列、downloadTask系列、uploadTask系列。具體看下面:app
//dataTask系列 - (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{ } //unloadTast系列 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler{ } - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler{ } //downloadTask系列 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler{ } - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler{ } - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler{ }
用dataTask下載一張圖片,而後用imageView顯示。ide
-(IBAction)requestBlockTaskTest:(id)sender{ [self clear]; NSURLSession *session = [NSURLSession sharedSession]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:bigPic]]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { UIImage *image = [[UIImage alloc]initWithData:data]; self.imageView.image = image; }]; [dataTask resume]; }
首先看一下NSURLSessionConfiguration
部分。從這個名字,咱們能夠預感到這個是與session的配置相關的,的確也是這樣。總共有三種類型的configuratin,另外還有不少屬性,好比配置緩存策略的requestCachePolicy
,請求超時的timeoutIntervalForRequest
,添加額外請求頭的HTTPAdditionalHeaders
,其餘還有不少屬性這裏就不一一說了,具體看源碼:學習
//默認的配置會將緩存存儲在磁盤上 @property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration; //第二種瞬時會話模式不會建立持久性存儲的緩存. @property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration; //第三種後臺會話模式容許程序在後臺進行上傳下載工做 + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier; //各類屬性 @property NSURLRequestCachePolicy requestCachePolicy; @property NSTimeInterval timeoutIntervalForRequest; @property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;
從上面NSURLSession
初始化一個請求的時候,咱們發現NSURLSessionTask
並不能直接使用,只能使用他的子類。具體以下:
NSURLSessionTask
抽象類。有對應的代理NSURLSessionTaskDelegate
,並且這個代理繼承了NSURLSessionDelegate
代理。
NSURLSessionDataTask
是NSURLSessionTask
的子類。有對應的代理NSURLSessionTaskDelegate
,並且這個代理繼承了NSURLSessionTaskDelegate
代理。咱們通常網絡請求,就用這個類。
NSURLSessionDownloadTask
是NSURLSessionTask
的子類。有對應的代理NSURLSessionDownloadDelegate
,並且這個代理繼承了NSURLSessionTaskDelegate
代理。這個主要用於下載大文件等。
NSURLSessionUploadTask
是NSURLSessionDataTask
的子類。有對應的代理及時父類代理NSURLSessionDownloadDelegate
。這個主要用於處理上傳請求如上傳圖片。
從上面咱們發現Task和delegate有一套對應的繼承關係:
NSURLSessionTask (抽象類,NSURLSessionTaskDelegate
)
NSURLSessionDataTask (NSURLSessionDataDelegate
)
NSURLSessionUploadTask (NSURLSessionDataDelegate
)
NSURLSessionDownloadTask (NSURLSessionDownloadDelegate
)
NSURLSessionDelegate
NSURLSessionTaskDelegate
NSURLSessionDataDelegate
NSURLSessionDownloadDelegate
從繼承關係上,咱們就能夠理解在初始化的時候,只經過設置NSURLSession
對象的delegate就能夠了。由於根據不一樣的task,其實就是設置了不一樣的delegate。這個設計避免了屢次設置delegate的狀況,同時也根據不一樣的task實現不一樣的delegate方法。真是一個很絕妙的設計。
接下來咱們看看NSURLSession
的delegate對象NSURLSessionDelegate
的方法,當一個session遇到錯誤、或者須要認證、應用進入後臺都會調用下面的代理方法:
//當一個session遇到系統錯誤或者未檢測到的錯誤的時候,就會調用這個方法。 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error{ } //當請求須要認證、或者https證書認證的時候,咱們就須要在這個方法裏面處理。 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ } //若是應用進入後臺、這個方法會被調用。咱們在這裏能夠對session發起的請求作各類操做好比請求完成的回調等。 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { }
/* 當請求重定向的時候調用這個方法。咱們必須設置一個新的`NSURLRequest`對象傳入completionHandler來重定向新的請求,可是當`session`是background模式的時候,這個方法不會被調用。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler{ } /* 當請求須要認證的時候調用這個方法。若是沒有實現這個代理,那麼請求認證這個過程不會被調用。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ } /* 若是請求須要一個新的請求體時,這個方法就會被調用。好比認證失敗的時候,咱們能夠經過這個機會重新認證。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler{ } /* 當咱們上傳數據的時候,咱們能夠經過這個代理方法獲取上傳進度。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{ } /* 當task的統計信息收集好了之後,調用這個方法。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics { } /* 當一個task出錯的時候,會調用這個方法。若是error是nil,也會調用這個方法,表示task完成。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error{ NSLog(@"數據返回之後,無論有錯沒錯都回調用,若是沒錯,error及時nil"); }
/* 當一個task接收到返回信息。當全部信息都接收完畢之後,completionHandler會被調用。咱們能夠在這裏取消一個網絡請求或者把一個datatask轉換爲downloadtask。若是沒有實現這個代理方法,咱們也能夠經過task的response屬性獲取到對應的數據。background模式的uploadtask不會調用這個方法。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{ } /* 當一個datatask轉換爲一個downloadtask之後會調用。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask{ // 容許處理服務器的響應,纔會繼續接收服務器返回的數據 completionHandler(NSURLSessionResponseAllow); } /* 暫時忽略,這個是和數據流相關的。無論了 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask{ } /* 當data可使用的時候,調用這個方法。咱們能夠在這裏獲取data。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ } /* 容許咱們在這裏調用completionHandler緩存data,或者傳入nil來禁止緩存 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{ }
/* 當一個下載task任務完成之後,這個方法會被調用。咱們能夠在這裏移動或者複製download的數據 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ } /* 獲取下載進度 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ } /* 重啓一個下載任務(好比下載一半後中止而後過一點時間繼續)。若是下載出錯,`NSURLSessionDownloadTaskResumeData`裏面包含從新開始下載的數據。 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ }
分別用三種不一樣方式下載一張圖片而後在imageView上顯示。
#import "ViewController.h" static NSString *const bigPic = @"http://i1.piimg.com/4851/d1498fea89ae3bc1.png"; static NSString *const smallPic = @"http://i1.piimg.com/4851/97aef4680d359905.png"; @interface ViewController ()<NSURLSessionDelegate> @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property(nonatomic,strong)NSMutableData *data; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (IBAction)requestDataTest:(id)sender { [self clear]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:bigPic]]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; [dataTask resume]; } - (IBAction)requestDownloadTest:(id)sender { [self clear]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:bigPic]]; NSURLSessionDownloadTask *dataTask = [session downloadTaskWithRequest:request]; [dataTask resume]; } -(IBAction)requestBlockTaskTest:(id)sender{ [self clear]; NSURLSession *session = [NSURLSession sharedSession]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:bigPic]]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { UIImage *image = [[UIImage alloc]initWithData:data]; self.imageView.image = image; }]; [dataTask resume]; } -(void)clear{ self.imageView.image = nil; } //==============================NSURLSessionDelegate======================== #pragma NSURLSessionDelegate //當一個session遇到系統錯誤或者未檢測到的錯誤的時候,就會調用這個方法。 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error{ } //當請求須要認證、或者https證書認證的時候,咱們就須要在這個方法裏面處理。 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ } //若是應用進入後臺、這個方法會被調用。咱們在這裏能夠對session發起的請求作各類操做好比請求完成的回調等。 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { } //==================================NSURLSessionTaskDelegate==================== #pragma NSURLSessionTaskDelegate /* 當請求重定向的時候調用這個方法。咱們必須設置一個新的`NSURLRequest`對象傳入completionHandler來重定向新的請求,可是當`session`是background模式的時候,這個方法不會被調用。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler{ } /* 當請求須要認證的時候調用這個方法。若是沒有實現這個代理,那麼請求認證這個過程不會被調用。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ } /* 若是請求須要一個新的請求體時,這個方法就會被調用。好比認證失敗的時候,咱們能夠經過這個機會重新認證。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler{ } /* 當咱們上傳數據的時候,咱們能夠經過這個代理方法獲取上傳進度。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{ NSLog(@""); } /* 當task的統計信息收集好了之後,調用這個方法。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics { } /* 當一個task出錯的時候,會調用這個方法。若是error是nil,也會調用這個方法,表示task完成。 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error{ NSLog(@"數據返回之後,無論有錯沒錯都回調用,若是沒錯,error及時nil"); if (self.data) { self.imageView.image = [UIImage imageWithData:self.data]; self.data = nil; } } //==================================NSURLSessionDataDelegate===================================== #pragma NSURLSessionDataDelegate /* 當一個task接收到返回信息。當全部信息都接收完畢之後,completionHandler會被調用。咱們能夠在這裏取消一個網絡請求或者把一個datatask轉換爲downloadtask。若是沒有實現這個代理方法,咱們也能夠經過task的response屬性獲取到對應的數據。background模式的uploadtask不會調用這個方法。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{ self.data = nil; self.data = [NSMutableData data]; // 容許處理服務器的響應,纔會繼續接收服務器返回的數據 completionHandler(NSURLSessionResponseAllow); } /* 當一個datatask轉換爲一個downloadtask之後會調用。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask{ } /* 暫時忽略,這個是和數據流相關的。無論了 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask{ } /* 當data可使用的時候,調用這個方法。咱們能夠在這裏獲取data。 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ [self.data appendData:data]; NSLog(@"具體數據在URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidCompleteWithError:(nullable NSError *)error處理"); } /* 容許咱們在這裏調用completionHandler緩存data,或者傳入nil來禁止緩存 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{ } //==================================NSURLSessionDownloadTask================================= #pragma NSURLSessionDownloadTask /* 當一個下載task任務完成之後,這個方法會被調用。咱們能夠在這裏移動或者複製download的數據 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSString *path = location.absoluteString; UIImage *image = [[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:location]]; self.imageView.image = image; NSLog(@"數據下載完成之後,會保存在一個location的地方。%@",location); } /* 獲取下載進度 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ NSLog(@"總得數據大小%lld----",bytesWritten); } /* 重啓一個下載任務(好比下載一半後中止而後過一點時間繼續)。若是下載出錯,`NSURLSessionDownloadTaskResumeData`裏面包含從新開始下載的數據。 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ } @end