iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶

今天博客的主題不是Alamofire, 而是iOS網絡編程中常用的NSURLSession。若是你想看權威的NSURLSession的東西,那麼就得去蘋果官方的開發中心去看了,雖然是英文的,可是結合代碼理解應該不難。更詳細的信息請移步於蘋果官方介紹URL Loading System,網上好多iOS網絡編程的博客都翻譯於此。由於目前iOS開發中,網絡請求大部分使用NSURLSession,因此今天的博客咱們就以NSURLSession展開。關於以前使用的NSURLConnection在此就不作過多贅述了。今天博客的主要內容是系統的介紹NSURLSession及其相關的Delegate,固然每一個知識點都依託於實例,若是你仔細的閱讀本篇博客仍是收穫很多的。php

下方這個截圖中所涵蓋的全部功能就是本篇博客中所涉及的全部知識點,幾乎涵蓋了NSURLSession的全部的東西。接下來咱們就一個一個的功能點來詳述一下NSURLSession。html

  

 

1、NSURLSession概覽

NSURLSession對於iOS開發來講並非什麼新的內容,它是Apple在iOS7中引入的,其主要功能是發起網絡請求獲取網絡數據,這與iOS7以前使用的NSURLConnection功能相似,可是NSURLSession更爲強大。若是在你開發的App中沒有使用第三方網絡庫,那麼NSURLSession無異因而最佳的選擇。雖然網上的關於NSURLSession的東西一抓一大把,可是每一個人都有每一個人的看法,今天的博客就係統的整理一下NSURLSession相關的知識點,算是爲下篇博客作準備吧。由於下篇博客是對Alamofire框架進行的解析,Alamofire就是對NSURLSession的封裝,仍是那句話,若是你對NSURLSession不熟悉的話,那麼Alamofire源碼看起來會比較費勁的。在本篇博客的第一部分咱們先總體的概覽一下NSURLSession,以便後面一步步的展開。ios

廢話少說,進入本篇博客的主題。從NSURLSession這個名字中咱們不難看出,主要是URL + Session。顧名思義,NSURLSession是用來URL會話的。固然若是你作過服務器端的開發,好比PHP,也會有Session的概念,不過此Session非彼Session,二者的區別仍是不小的。iOS的NSURLSession的主要功能是經過URL與服務器簡歷會話的。「會話」進一步說就是交流唄,一句話總結:也就是咱們的iOS客戶端可使用NSURLSession這個東西經過相應的URL與咱們的服務器創建會話,而後經過此會話來完成一些交互任務(NSURLSessionTask)。Session有着不一樣的類型,每種類型的Session又能夠執行不一樣類型的任務(Task)。接下來就來介紹一下Session的類型以及所執行的任務等。git

1.NSURLSession的類型

在使用NSURLSession時你得知道你使用的是那種類型的Session對吧。從官方的NSURLSession API中不難看出,公有三種類型的Session:Default sessions,Ephemeral sessions,Background sessions這三種Session咱們能夠經過NSURLSessionConfiguration來指定。github

  • 默認會話(Default Sessions)使用了持久的磁盤緩存,而且將證書存入用戶的鑰匙串中。
  • 臨時會話(Ephemeral Session)沒有像磁盤中存入任何數據,與該會話相關的證書、緩存等都會存在RAM中。所以當你的App臨時會話無效時,證書以及緩存等數據就會被清除掉。
  • 後臺會話(Background sessions)除了使用一個單獨的線程來處理會話以外,與默認會話相似。不過要使用後臺會話要有一些限制條件,好比會話必須提供事件交付的代理方法、只有HTTP和HTTPS協議支持後臺會話、老是伴隨着重定向。僅僅在上傳文件時才支持後臺會話,當你上傳二進制對象或者數據流時是不支持後臺會話的。當App進入後臺時,後臺傳輸就會被初始化。(須要注意的是iOS8和OS X 10.10以前的版本中後臺會話是不支持數據任務(data task)的)。

 下方的截圖就是咱們使用Swift語言建立了上述三種類型的會話配置,Session在初始化時能夠指定下方的任意一種SessionConfiguration。具體入校所示:數據庫

  

 

2. NSURLSession的各類任務

在一個Session會話中能夠發起的任務可分爲三種:數據任務(Data Task)、下載任務(Download Task)、上傳任務(Upload Task)。上面也提到了,在iOS8和OS X 10.10以前的版本中後臺會話是不支持Data Task。下面來簡述一下這三種任務。編程

  • Data Task(數據任務)負責使用NSData對象來發送和接收數據。Data Task是爲了那些簡短的而且常常從服務器請求的數據而準備的。該任務能夠沒請求一次就對返回的數據進行一次處理。
  • Download task(下載任務)以表單的形式接收一個文件的數據,該任務支持後臺下載。
  • Upload task(上傳任務)以表單的形式上傳一個文件的數據,該任務一樣支持後臺下載。

上面所介紹的全部類型的Session以及Session中的Task會在下方的實例中進行一一的介紹,本部分就作一個概述。swift

 

2、URL編碼

1.URL編碼概述

不管是GET、POST仍是其餘的請求,與服務器交互的URL是須要進行編碼的。由於進行URL編碼的參數服務器那邊才能進行解析,爲了能和服務器正常的交互,咱們須要對咱們的參數進行轉義和編碼。先簡單的聊一下什麼是URL吧,其實URL是URI(Uniform Resource Identifier ---- 統一資源定位符)的一種。URL就是互聯網上資源的地址,用戶能夠經過URL來找到其想訪問的資源。RFC3986文檔規定,Url中只容許包含英文字母(a-zA-Z)、數字(0-9)、-_.~4個特殊字符以及全部保留字符,若是你的URL中含有漢字,那麼就須要對其進行轉碼了。RFC3986中指定了如下字符爲保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]api

在URL編碼時有必定的規則,下方是咱們今天主要使用的URL格式的一個規則的一個圖解。其餘的咱們先不說,今天博客中所涉及的主要是下圖中Query的部分。從下面咱們不難看出,Path和Query之間使用的是?號進行分隔的,問好後邊就是咱們要傳給服務武器的參數了,該參數就是下方的Query的部分。在Get請求中Query是存放在URL後邊,而在POST中是放在Request的Body中。若是你的參數只是一個key-Value, 那麼Query的形式就是key = value。若是你的參數是一個數組好比key = [itme1, item2, item3,……],那麼你的Query的格式就是key[]=item1&key[itme2]&key[item3]……。若是你的參數是一個字典好比key = ["subKey1":"item1", "subKey2":"item2"], 那麼Query對應的形式就是key[subKey1]=item1&key[subKey2]=item2.接下來咱們要作的就是將字典進行URL編碼。數組

 

2.將Dictionary進行URL編碼

iOS開發中,有時候咱們從VC層或者VM層獲取到的數據是一個字典,字典中存儲的就是要發給服務器的數據參數。直接將字典轉成二進制數據發送給服務器,服務器那邊是無法解析iOS這邊的字典的,得有一個統一的交互標準,這個標準就是URL編碼。咱們要作的就是講字典進行URL編碼,而後將編碼後的東西在傳給服務器,這樣一來服務器那邊就能解析到咱們請求的參數了。下方摺疊的這段代碼就是從AlamoFire框架中摘抄出來的三個方法,位於ParameterEncoding.swift文件中。該段代碼就是負責將字典類型的參數進行URL編碼的,在編碼過程當中進行轉義是少不了的。

 1     // - MARK - Alamofire中的三個方法該方法將字典轉換成URL編碼的字符
 2     func query(parameters: [String: AnyObject]) -> String {
 3         
 4         var components: [(String, String)] = []     //存有元組的數組,元組由ULR中的(key, value)組成
 5         
 6         for key in parameters.keys.sort(<) {        //遍歷參數字典
 7             let value = parameters[key]!
 8             components += queryComponents(key, value)
 9         }
10         
11         return (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")
12     }
13     
14     
15     func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] {
16         var components: [(String, String)] = []
17         
18         
19         if let dictionary = value as? [String: AnyObject] {         //value爲字典的狀況, 遞歸調用
20             for (nestedKey, value) in dictionary {
21                 components += queryComponents("\(key)[\(nestedKey)]", value)
22             }
23         } else if let array = value as? [AnyObject] {               //value爲數組的狀況, 遞歸調用
24             for value in array {
25                 components += queryComponents("\(key)[]", value)
26             }
27         } else {  //vlalue爲字符串的狀況,進行轉義,上面兩種狀況最終會遞歸到此狀況而結束
28             components.append((escape(key), escape("\(value)")))
29         }
30         
31         return components
32     }
33     
34     /**
35      
36      - parameter string: 要轉義的字符串
37      
38      - returns: 轉義後的字符串
39      */
40     func escape(string: String) -> String {
41         /*
42          :用於分隔協議和主機,/用於分隔主機和路徑,?用於分隔路徑和查詢參數, #用於分隔查詢與碎片
43          */
44         let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
45         
46         //組件中的分隔符:如=用於表示查詢參數中的鍵值對,&符號用於分隔查詢多個鍵值對
47         let subDelimitersToEncode = "!$&'()*+,;="
48         
49         let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet
50         allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode)
51         
52         
53         var escaped = ""
54         
55         //==========================================================================================================
56         //
57         //  Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
58         //  hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
59         //  longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
60         //  info, please refer to:
61         //
62         //      - https://github.com/Alamofire/Alamofire/issues/206
63         //
64         //==========================================================================================================
65         
66         if #available(iOS 8.3, OSX 10.10, *) {
67             escaped = string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? string
68         } else {
69             let batchSize = 50      //一次轉義的字符數
70             var index = string.startIndex
71             
72             while index != string.endIndex {
73                 let startIndex = index
74                 let endIndex = index.advancedBy(batchSize, limit: string.endIndex)
75                 let range = startIndex..<endIndex
76                 
77                 let substring = string.substringWithRange(range)
78                 
79                 escaped += substring.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? substring
80                 
81                 index = endIndex
82             }
83         }
84         
85         return escaped
86     }
View Code

在上述代碼中,功能並不算複雜。就是遞歸將字典中的全部鍵值對轉變成key=value、key[]=value、key[subkey]=value這三種形式。之因此進行遞歸,由於字典中有可能含有字典或者數組,數組中又可能嵌套着數組或者字典。全部要進行遞歸,直到找到key=value這種形式爲止。上述的三個函數中queryComponents()方法就負責進行遞歸調用的。從下方的截圖中咱們不難看出,字典、數組以及鍵值對的處理方式是不一樣的。

  

 

調用上述代碼段的query方法就能夠對字典進行轉義。query()方法的參數是一個[String, AnyObject]類型的字典,返回參數是一個字符串。這個返回的字符串就是將該字典進行編碼後的結果。接下來咱們對其進行測試。點擊「URL編碼」按鈕就會執行下方的方法,在該方法中咱們定義了一個字典,該字典的key是String類型的,Value中存儲的有String、Array以及Dictionary。將該字典做爲參數傳入query()中,而後query()函數返回的字符串進行數據。緊跟着的就是輸出結果,從結果中咱們能看出將中文字符進行了百分號編碼,也就是URL編碼。

  

 

咱們能夠將上述輸出的字符串使用站上工具進行URL解碼,解碼後的URL以下所示:

  

 

 

3、數據任務--NSURLSessionDataTask

解決完了URL編碼的問題,咱們就具體的來看一下NSURLSessionDataTask了,也就是咱們上面所提到的Data Task。由於會話中會執行不一樣的任務,因此任務的對象來自於Session對象,也就是說咱們須要使用已經存在的Session對象來建立咱們的任務對象。接下來咱們來看一下Data Task的使用。本部分主要給出了Data Task的工做方式。

 

1.對Data task代碼的封裝

下方截圖中的sessionDataTaskRequest()方法,該方法的第一個參數是會話請求的方式「POST」、"GET"等。第二個參數就發送到服務器的參數,該參數是一個[String:AnyObject]類型的字典。下面就是NSURLSessionDataTask的使用步驟

  • 首先咱們先建立會話使用的URL,在建立URL是咱們要對parameters字典參數進行URL編碼。若是是GET方式的請求的話就使用?號將咱們編碼後的字符串拼接到URL後方便可。
  • 而後建立咱們會話使用的請求(NSURLMutableRequest),在建立請求時咱們要指定請求方式是POST仍是GET。若是是POST方式,咱們就將編碼後的URL字符串放入request的HTTPBody中便可,有一點須要注意的是咱們傳輸的數據都是二進制的,因此在將字符串存入HTTPBody以前要將其轉換成二進制,在轉換成二進制的同時咱們使用的是UTF8這種編碼格式。
  • 建立完Request後,咱們就該建立URLSession了,此處咱們爲了簡單就獲取了全局的Session單例。咱們使用這個Session單例建立了含有Request對象的一個DataTask。在這個DataTask建立時,有一個尾隨閉包,這個尾隨閉包用來接收服務器返回來的數據。固然此處能夠指定代理,使用代理來接收和解析數據的,稍後會介紹到。
  • 最後切記建立好的Data Task是處於掛起狀態的,須要你去喚醒它,因此咱們要調用dataTask的resume方法進行喚醒。具體以下所示。

 

2. 測試

上述Data Task的核心代碼已經完成,接下來咱們要對其進行Get和Post測試。也就是給上述方法傳入「GET」或者"POST"請求方式和相應的參數。下方代碼截圖是對DataTask進行GET測試。傳入相應的參數,控制檯中輸出的是服務器接收到參數後返回的數據。固然下方輸出的數據是咱們經過JSON解析後的數據了。

  

緊接着咱們進行POST測試,也就是傳入"POST"已經相應的參數,具體以下所示。下方的輸出是服務器返回的數據。

  

 

4、上傳任務---Upload Task

接下來咱們來搞一下Upload Task,顧名思義Upload Task就是用來往服務器上上傳東西的嘛。下方這個代碼段就是用來往服務器上傳二進制數據的,固然咱們使用的是POST方式進行表單提交的。下方的代碼步驟與上述DataTask的使用方式大爲類似,具體步驟以下所示。

  • 先建立URL和request併爲request指定請求方式爲POST。
  • 而後建立Session,此處咱們使用SessionConfiguration爲Session的類型指定爲default session類型。併爲該Session指定代理對象爲self。
  • 最後使用Session來建立upload task,在建立upload task時爲上傳任務指定NSURLRequest對象,而且傳入要上傳的表單數據formData,固然不要忘了將任務進行喚醒。

  

 

接下來咱們要將上述代碼進行測試,上面有兩測試地址,第一個是你可使用的,第二個是我在我本地服務器本身使用php寫的一個文件上傳的腳本,固然你是使用不了的。若是你要運行上述代碼的話,你就要使用第一個地址進行測試了。下方代碼段就是咱們的測試用例,首先咱們先經過網絡獲取圖片,並NSData加載到本地,獲取到圖片的二進制數據imageData。等待圖片數據獲取完畢後,在調用上述上傳數據的方法。爲了請求完圖片的二進制數據後在調用上述方法,咱們使用了GCD中dispatch group的相關東西。關於GCD更爲詳細的內容請參見以前的博客《GCD詳解》。下方的代碼會在點擊「UploadTask」按鈕時會被觸發。

  

 

在上傳文件時,若是你想時刻的監聽上傳的進度,你能夠去實現NSURLSessionTaskDelegate中的didSendBodyData方法,該方法會實時的監聽文件上傳的速度。bytesSent回調參數表示本次上傳的字節數,totalBytesSend回調參數表示已經上傳的數據大小,

totalBytesExpectedToSend表示文件公有的大小。該回調方法具體實現方式以下,在下方回調方法中咱們根據每次上傳的數據狀況對進度條進行更新,固然在更新UI時咱們要在主線程中進行更新。具體代碼以下。

 

 

5、下載任務--Download Task

Download Task這種類型的任務就稍微有些複雜了,接下來咱們來一一的進行介紹。接下來咱們要實現一個支持後臺下載而且支持暫停和繼續的任務。在下載時咱們也要實現相應的回調代理來監聽下載進度,後臺下載以及下載任務的暫停和繼續在開發中用的仍是比較多的,本部分就好好的探討一下Download task。下方的實例是從網絡下載一個比較大的圖片,下載完畢後就從存儲到Document中。

1.建立後臺會話

在建立Download Task 以前咱們要先建立一個支持後臺下載的會話,也就是Background Session。由於咱們要暫停和續傳,因此在此Background Session的對象和Download Task的對象都是使用的類屬性。下方代碼段就建立了一個background session的對象。首先咱們先建立一個background類型的Session Configuration,而後在建立downloadSession對象時配置爲background便可。在建立Session對象時要爲downloadSession對象指定代理對象,由於咱們要在相應的代理對象中獲取下載進度更新咱們的ProgressView。建立DownloadSession對象的代碼以下:

1         //建立BackgroundDownloadSession
2         let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(keyBackgroundDownload)
3         self.downloadSession = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)

 

2.開始下載

建立好downloadSession對象後,咱們就該建立downloadTask開始進行文件的下載了。下方代碼段就是點擊「開始下載」按鈕所觸發的方法。首先咱們先獲取ResumeData,這個ResumeData就是咱們暫停下載任務是所保存的信息,經過該ResumeData咱們能夠接着上次的文件進行下載。ResumeData中存儲的並非咱們上次下載的數據Data,而是存儲了下載地址和上次下載的位置等相關的信息,稍後會將ResumeData進行打印。咱們從UserDefault中獲取ResumeData,若是存在ResumeData咱們就調用下載會話的downloadTaskWithResumeData()方法傳入ResumeData接着上次的下載。若是ResumeData爲nil,那麼咱們就建立下載請求,調用下載會話的downloadTaskWithRequest()方法建立下載任務。建立完下載任務後不要忘記將任務進行resume()呢。點擊「開始下載」的代碼以下所示。

  

 

3.暫停下載

上面是開始下載,接下來讓咱們來實現暫停下載。下方代碼段就是點擊「暫停下載」按鈕所觸發的方法。在該方法中咱們主要調用了downloadTask中的cancelByProducingResumeData()方法來進行任務的暫停的。在調用上述方法時會經過Closure回調的形式返回一個ResumeData,此處的ResumeData就是上面咱們使用到的ResumeData。拿到該ResumeData後你要講它進行磁盤的持久化存儲,便於下次繼續下載。此處爲了方便就將ResumeData存到了UserDefault中,其實也就是plist文件中。

  

下方就是咱們在暫停下載任務時所打印的ResumeData中的內容。從下方的內容不難看出ResumeData就是一個xml格式的文本信息其中存儲着相應的下載信息。好比下載文件的URL(NSURLSessionDownloadURL),已經接受的數據字節(NSURLSessionResumeBytesReceived)等等相關的下載信息,具體請看下圖。咱們能夠經過下方的xml存儲的信息從新接着上次的下載任務進行下載。

  

上面有一項存儲的就是所下載文件的臨時文件的名稱,就位於temp目錄中。在下載過程當中正在下載的任務會在temp目錄中建立一個.tmp的臨時文件用來存儲下載的臨時數據,也就是說這個臨時文件就是邊下邊存的地方。下載完成後咱們要對該臨時文件進行轉存的,由於下載完成後該臨時文件會被自動刪除的。

  

 

4.下載任務的回調--NSURLSessionDownloadDelegate

上面兩段代碼主要是用於下載任務的開始和暫停的,若是你要想對下載完成後的文件進行處理,以及要監聽下載進度的話,就得實現NSURLSessionDownloadDelegate代理中相應的方法了。NSURLSessionDownloadDelegate中有3個代理方法,分別負責處理文件下載完成,監測下載進度以及文件暫停時的處理工做。接下來將要結合上述下載任務的開始和暫停的代碼來探討一下這三個代理方法。

(1)、文件下載完成後的回調----didFinishDownloadingToURL

下方代碼段中的代理方法就是在文件下載完成後要執行的回調方法。在下方的委託回調方法中有三個回調參數,第一個就是咱們的downloadSession對象,第二個參數就是咱們的downloadTask對象,第三個參數就是臨時文件的下載目錄。臨時文件在下載完成以後若是你不作任何處理的話,那麼就會被自動刪除。下方代碼段在獲取臨時文件路徑後將臨時文件使用FileManager將臨時文件存儲到相應的文件夾中,新文件的名字此處取的是當前時間的時間戳,以下所示。

  

 

(2)、監聽下載任務----didWriteData

下方代碼片斷是用來實時監聽下載進度的回調方法,該方法中有5個回調參數。前兩個就不說了,重點在後三個。didWriteData參數是本次下載的數據,單位爲字節,totalBytesWritter參數表明着已經下載的數據總量,totalBytesExpectedToWrite是文件的總量。經過上述三個參數咱們不難計算出當前的下載進度,能夠在該委託回調方法中進行ProgressiView的更新。具體代碼以下所示

  

 

 

(3)暫停後再次啓動下載任務的代理方法----didResumeAtOffset

下方回調方法會在暫停的下載任務重啓後會被調用。該代理回調方法中有四個回調參數,前兩個就很少說了,咱們來看後兩個。fileOffset表明中已經下載文件的大小,expectedTotalBytes表示文件的總大小,以下所示:

  

至此,NSURLSessionDownloadDelegate中的三個代理方法已介紹完畢。在你作文件下載時上述回調大部分狀況下會被使用到。

 

 

6、網絡緩存

網絡緩存在網絡請求中使用的仍是蠻多的,尤爲是加載一些H5頁面時常常會加一些緩存來提升用戶體驗。有時的一些數據也會進行緩存,你可將數據緩存到你的SQLite數據庫、PList文件,或者直接使用NSURLSession相關的東西進行緩存。接下來要介紹的緩存方式就是網絡緩存,就是利用NSURLSession相關的類來實現網絡緩存。該緩存的過程不須要你去操做數據庫或者plist文件,下方給出了四種不一樣的網絡緩存方式,不管是哪種網絡緩存的方式只是用法不同,本質上是同樣的,都是利用NSURLSession進行的網絡緩存。廢話少說,進入該部分的主題。

1.緩存策略概述

在配置網絡請求緩存時,有着不一樣的請求緩存策略。下方就是全部支持的網絡緩存策略:

  • UseProtocolCachePolicy -- 緩存存在就讀緩存,若不存在就請求服務器

  • ReloadIgnoringLocalCacheData -- 忽略緩存,直接請求服務器數據

  • ReturnCacheDataElseLoad -- 本地若有緩存就使用,忽略其有效性,無則請求服務器

  • ReturnCacheDataDontLoad -- 直接加載本地緩存,沒有也不請求網絡 

  • ReloadIgnoringLocalAndRemoteCacheData -- 尚未實現

  • ReloadRevalidatingCacheData -- 尚未實現

上述緩存策略在Foundation框架中是以枚舉的形式來提現的,該緩存策略的枚舉類型是NSURLRequestCachePolicy,具體定義以下所示:

  

 

2.使用NSMutableURLRequest指定緩存策略

接下來咱們使用NSMutableURLRequest來指定緩存策略,在NSMutableURLRequest類的對象中有一個參數cachePolicy用來指定緩存策略的,只須要將上述枚舉的緩存策略的枚舉值賦值給cachePolicy便可。下方代碼段就是點擊「Request設置緩存」按鈕所觸發的代碼,在下方代碼中咱們使用DataTask對百度的網頁進行請求,將請求的數據使用.ReturnCacheDataElseLoad的緩存策略進行緩存。下方紅框的部分就是使用NSMutableURLRequest對象來設置緩存策略的代碼,具體以下所示:

  

 

下方就是點擊「Request設置緩存」按鈕後所呈現的效果,緩存目錄默認爲~/Library/Caches/[Boundle ID]/fsCachedData/緩存文件,緩存文件名是按照必定的規則生成的,固然同一個URL所生成的緩存文件名是相同的。下方就是咱們所緩存的文件,使用Sublime打開后里邊就是百度的HTML頁面。以下所示。當緩存完畢後,若是你再次發起請求的話就會從緩存文件中進行數據的加載。

  

 

3. 使用NSURLSessionConfiguration指定緩存策略

除了直接使用Request對象來指定請求緩存策略,咱們還可使用NSURLSessionConfiguration的對象來指定緩存策略。在NSURLSessionConfiguration類中有一個用來設置請求緩存策略的requestCachePolicy屬性。使用該屬性設置的緩存策略時,一樣的緩存策略所表現的效果與上面直接使用NSURLMutableRequest設置的緩存策略表現是一致的。下方代碼段就是使用NSURLSessionConfiguration對象來設置緩存策略,以下所示:

  

 

因爲此處的緩存文件與上述一致,若是該請求鏈接以被上面緩存就會被直接加載。

 

4.使用URLCache + request進行緩存

上面是使用URLRequest自帶的緩存策略,可定製性和靈活度比較低。若是要對網絡緩存有着較高的定製性的話,咱們就得使用NSURLCache這個東西了。雖然NSURLURLCache任然依賴於NSURLRequst對象,不過能夠設置一些緩存的參,好比緩存路徑、緩存的最大磁盤容量和內存容量等等。接下來咱們就要使用URLCache來進行網絡緩存了。下面的代碼就是對「博客園」首頁的HTML進行的緩存,固然咱們在此使用的是URLCache。

在下方代碼中咱們先建立了三個常量:memoryCapacity--緩存最大內存容量、diskCapacity--緩存最大磁盤容量、cacheFilePath--緩存路徑。上面這三個常量用來做爲初始化NSURLCache對象的參數,建立完NSURLCache對象後咱們將其設置成全局的URLCache。緩存策略仍然使用NSURLMutableRequest來指定。具體代碼以下所示。

  

 

有一點須要注意的是此處設置的緩存路徑是相對於/Library/Caches/[Boundle ID]/的,會在這個至關路徑下建立相應的文件夾來存放緩存文件。下方就是咱們使用NSURLCache緩存的文件路徑已經內容,從內容不難看出就是博客園首頁的HTML代碼。效果以下所示:

  

 

五、使用URLCache + NSURLSessionConfiguration進行緩存

你也能夠在NSURLSessionConfigurationzhon中指定URLCache對象,固然此處咱們使用NSURLSessionConfiguration的對象來指定緩存策略。NSURLSessionConfiguration對象中有一個屬性是URLCache, 咱們能夠用它來配置URLCache對象。下方代碼就是使用NSURLSessionConfiguration結合着URLCache進行緩存的。緩存效果與上面的一致。

  

 

六、清除緩存

誰污染誰治理呢,建立完緩存,若是在不用時咱們要對相應的緩存數據進行清理的。清理緩存就是找到緩存所在的文件夾將緩存的文件進行刪除便可。下方代碼段就是對咱們上面建立的全部緩存進行清理。由於下方的每行代碼基本上都有註釋,在此就對其作過多的解釋了。主要仍是NSFileManager的使用。以下所示:

  

 

 

7、請求認證

有時爲了網絡請求的安全性,服務器與客戶端之間要進行身份的驗證。根據安全性的不一樣要求能夠是單向驗證,也能夠是雙向驗證。本部分咱們就來聊一下NSURLSession發起網絡請求遇到驗證時的處理方案,就以HTTPS證書驗證爲例。下方會先介紹認證方式與認證策略,而後結合實例來進一步認識NSURLSession中的請求認證。

1.認證方式

首先咱們先來大致的瞭解一下全部的認證方式

  • NSURLAuthenticationMethodHTTPBasic: HTTP基本認證,須要提供用戶名和密碼
  • NSURLAuthenticationMethodHTTPDigest: HTTP數字認證,與基本認證類似須要用戶名和密碼
  • NSURLAuthenticationMethodHTMLForm: HTML表單認證,須要提供用戶名和密碼
  • NSURLAuthenticationMethodNTLM: NTLM認證,NTLMNT LAN Manager)是一系列旨向用戶提供認證,完整性和機密性的微軟安全協議
  • NSURLAuthenticationMethodNegotiate: 協商認證
  • NSURLAuthenticationMethodClientCertificate: 客戶端認證,須要客戶端提供認證所需的證書
  • NSURLAuthenticationMethodServerTrust: 服務端認證,由認證請求的保護空間提供信任

上面後兩個就是咱們在請求HTTPS時會遇到的認證,須要服務器或者客戶端來提供認證的,這個證書就是咱們平時常說的CA證書。固然你也可使用自簽名證書了,這就不在本篇博客的討論範圍內了。

 

2.認證處理策略

當咱們進行網絡求時,會對相應的認證作出響應。在NSURLSession進行網絡請求時支持四種證書處理策略,這些認證處理策略以枚舉的形式來存儲,枚舉的類型爲NSURLSessionAuthChallengeDisposition。下方就是認證的全部處理策略:

  • UseCredential 使用證書
  • PerformDefaultHandling 執行默認處理, 相似於該代理沒有被實現同樣,credential參數會被忽略
  • CancelAuthenticationChallenge 取消請求,credential參數一樣會被忽略
  • RejectProtectionSpace 拒絕保護空間,重試下一次認證,credential參數一樣會被忽略

 

3.HTTPS請求證書處理

接下來咱們就根據實例來感覺一下上述的認證方式以及認證處理策略,在此咱們就以HTTPS的證書認證爲例。點擊「NSURLAuthenticationChallenge」按鈕就會執行下方代碼段,在下方代碼段中咱們以請求宜信--星火金服的首頁的HTML數據爲例。由下方的代碼段咱們能夠看出星火金服的首頁是https,咱們在請求該頁面數據時,確定會進行證書認證的處理的。下方咱們使用的默認會話中的Data Task發起的https請求。

  

 

發起上述https請求後,就會執行下方的代理方法。下方的委託代理方法屬於NSURLSessionDelegate中處理認證的方法,也就是若是服務器須要認證時就會執行下方的回調方法。下方代碼首先從受權質疑的保護空間中取出認證方式,而後根據不一樣的認證方式進行不一樣的處理。下方給出了兩種認證方式的處理,上面的if語句塊賦值服務端認證,下面的if語句塊負責HTTP的基本認證。具體處理方式以下所示。有一點須要注意的是若是在該委託回調方法中若是不執行completionHandler閉包,那麼認證就會失效,是請求不到數據的。

  

 

 

8、NSURLSession相關代理

在AlamoFire框架中用到了好多的NSURLSession的相關代理,AlamoFire框架對NSURLSession的相關代理進行了封裝,使用Closure的形式進行了替換,因此在閱讀AlamoFire源碼以前瞭解NSURLSession的相關代理方法的功能比較重要的。接下來將要對NSURLSession全部相關的代理方法進行介紹,固然上面已經用到的代理方法在該部分就不重述了。下面的內容首先會總體的介紹一些這些代理的關係,而後各個擊破。

1.SessionDelegate類圖

下方類圖是SessionDelegate相關協議已經SessionTask相關類之間的繼承和依賴關係。上面已經介紹了各類Session Task的使用,固然除了Stream Task以外。Stream Task是iOS9以後添加的東西,用來進行數據流的請求與交互的,在此就很少說了。該部分是對下方類圖中上半部分進行介紹。Session相關的Delegate都繼承在NSURLSessionDelegateDownloadDelegate、DataDelegate、StreamDelegate則繼承自SessionTaskDelegate。詳細的請看下方類圖。

  

 

2.Delegate測試用例

爲了進行各類代理的測試,咱們建立了下方專門用於代理測試的請求。網絡請求的地址使用的是「https://www.xinghuo365.com」,後面沒有加index.shtml。由於直接請求域名星火金服會進行重定向,正好在咱們相應的代理方法中進行請求重定向的處理。點擊「SessionDelegate」按鈕就會執行下方的方法。

  

 

3.NSURLSessionDelegate

上面咱們在證書認證時實現了一個didReceiveChallenge代理方法,該方法就位於NSURLSessionDelegate代理中。在NSURLSessionDelegate代理中除了didReceiveChallenge代理方法外還有兩個方法。下方截圖中就是這兩個代理方法,

didBecomeInvalidWithError代理方法會在Session無效後被調用,URLSessionDidFinishEventsForBackgroundURLSession該代理方法會在後臺Session在執行完後臺任務後所執行的方法。

  

 

 

4.NSURLSessionTaskDelegate

接下來咱們來介紹NSURLSessionDelegate的子協議NSURLSessionTaskDelegate,固然父協議中的代理方法一樣適用於全部的子協議的。關於NSURLSessionTaskDelegate的代理方法,上面咱們在介紹UploadTask時用到了NSURLSessionTaskDelegate協議中的

didSendBodyData代理方法來監聽上傳速度。接下來咱們來介紹該代理方法中的其餘代理方法。

(1).請求的重定向

當咱們請求的地址進行重定向時會執行NSURLSessionTaskDelegate中的willPerformHTTPRedirection方法,咱們能夠在此代理方法中對重定向的請求進一步的進行處理,甚至在此進行重定向。下方代碼段的截圖就是該URL重定向後要執行的方法,咱們在此方法中將重定向的內容再次進行重定向,咱們此處是重定向到的百度。具體作法以下所示。

  

(2)、其餘代理方法

下方代碼片斷中的三個代理方法是NSURLSessionTaskDelegate中其餘的代理方法,下方第一個方法是用來處理認證策略的,與NSURLSessionDelegate中的認證代理使用方式一致,若是你已經實現了NSURLSessionDelegate中的相應的方法,那麼此處的認證方法不會被調用。第二個是關於流操做的,由於至今沒有真正用過流試的請求方式再次就不作過多的贅述了。第三個是Session Task執行完畢後會調用的方法,具體以下所示。

 

5.NSURLSessionDataDelegate

NSURLSessionDataDelegate中的方法主要是用來處理Data Task任務相應的事件的。在介紹NSURLSessionDataDelegate中具體的代理方法以前咱們先了解一下NSURLSession中對Data Task相應數據的處理策略。瞭解完處理策略之後,咱們再來一個接一個的介紹NSURLSessionDataDelegate中所對應的回調方法。

(1)、相應處理策略

在Data Task收到相應後,咱們能夠經過相應的代理方法指定處理策略,全部的處理策略一樣是以枚舉的形式存在的。枚舉類型NSURLSessionResponseDisposition中存儲的就是Data Task的響應處理策略,共有四種處理策略,下方是每種響應處理策略的詳細介紹:

  • Cancel :取消數據的加載,默認爲 .Cancel。此處理方式就是忽略數據的加載,取消對響應數據的進一步解析。
  • Allow :容許繼續操做, 會執行 NSURLSessionDataDelegate中的dataTaskDidReceiveData回調方法

  • BecomeDownload : 將Data Task的響應轉變爲DownloadTask,會執行NSURLSessionDownloadDelegate代理中相應的方法

  • BecomeStream : 將Data Task的響應轉變爲DownloadTask,會執行NSURLSessionStreamDelegate代理中相應的方法

 

(2)、Data Task接收到響應後執行的方法--didReceiveResponse

下方的回調方法會在咱們執行Data Task時受到服務器響應時所回調的方法,在該方法中咱們就能夠指定上述相應的處理策略。下方咱們指定的處理策略是Allow,就是容許繼續執行數據的請求和處理。

   

 

(3)、受到數據後執行的代理方法--didReceiveData

下方的代理方法就是在執行Data Task時,收到服務器的數據後所執行的方法。也就是上面的處理策略設置成Allow後會執行下方的方法,若是響應處理策略不是Allow那麼就不會接收到服務器的Data,從而也不會執行下面的方法。在該方法中咱們收到了服務器所返回的二進制數據,下方咱們將二進制數據轉成UTF8的字符串編碼。具體代碼以下所示:

  

 

(4)、任務轉變所執行的代理方法--didBecomeDownloadTask與didBecomeStreamTask

若是你處理響應的策略是BecomeDownload,那麼就會執行下方的第一個回調方法。若是處理策略是BecomeStream那麼就會執行下方第二個回調方法。

   

 

(5)、將要進行緩存響應----willCacheResponse

若是你在執行Data Task時,若是指定了響應的緩存策略,那麼在請求數據完畢會會執行下方的willCacheResponse代理方法。顧名思義,willCacheResponse就是在將要進行緩存的使用多調用的,具體作法以下:

  

NSURLSessionStreamDelegate是iOS9上提供的,若是Data被轉換成流數據就會執行NSURLSessionStreamDelegate中相應的方法,在streamTask中有一個readDataOfMinLength方法能夠讀取流中的數據。由於至今還用過NSURLSessionStreamDelegate,因此關於NSURLSessionStreamDelegate的東西就不作過多贅述了。不過在Github上分享的demo中有NSURLSessionStreamDelegate的相關內容,在此就不作過多的贅述了。

 

9、監測網絡鏈接狀態

本部分不屬於NSSession範疇,不過網絡開發怎麼能少的了監測網絡狀態的模塊呢。接下來咱們將要使用SystemConfiguration來實現reachability。在AlamoFire中也是使用的SystemConfiguration相關的內容來實現的reachability。

下方這個代碼段就是使用SystemConfiguration相關的內容來進行網絡狀態的監測。首先咱們先使用SCNetworkReachabilityCreateWithName來建立一個reachability對象,而後建立reachability的上下文,以後在設置網絡狀態改變後的回調,隨後將reachability對象放到隊列中進行執行,具體步驟具體代碼以下所示:

  

下方代碼段是咱們設置的網絡狀態改變後的回調方法,在其中對網絡的狀態進行了處理,具體代碼以下所示,因這部分比較簡單因此在此就不作過多贅述了。 

  

篇幅有限今天博客算是長篇大論了,就先到此,下篇博客會對AlamoFire源碼進行解析。

上述因此代碼Github分享地址爲:https://github.com/lizelu/NSURLSessionDemo

相關文章
相關標籤/搜索