天天看點

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

今天部落格的主題不是Alamofire, 而是iOS網絡程式設計中經常使用的NSURLSession。如果你想看權威的NSURLSession的東西,那麼就得去蘋果官方的開發中心去看了,雖然是英文的,但是結合代碼了解應該不難。更詳細的資訊請移步于蘋果官方介紹URL Loading System,網上好多iOS網絡程式設計的部落格都翻譯于此。因為目前iOS開發中,網絡請求大部分使用NSURLSession,是以今天的部落格我們就以NSURLSession展開。關于之前使用的NSURLConnection在此就不做過多贅述了。今天部落格的主要内容是系統的介紹NSURLSession及其相關的Delegate,當然每個知識點都依托于執行個體,如果你仔細的閱讀本篇部落格還是收獲不少的。

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

  

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

一、NSURLSession概覽

NSURLSession對于iOS開發來說并不是什麼新的内容,它是Apple在iOS7中引入的,其主要功能是發起網絡請求擷取網絡資料,這與iOS7之前使用的NSURLConnection功能類似,但是NSURLSession更為強大。如果在你開發的App中沒有使用第三方網絡庫,那麼NSURLSession無異于是最佳的選擇。雖然網上的關于NSURLSession的東西一抓一大把,但是每個人都有每個人的見解,今天的部落格就系統的整理一下NSURLSession相關的知識點,算是為下篇部落格做準備吧。因為下篇部落格是對Alamofire架構進行的解析,Alamofire就是對NSURLSession的封裝,還是那句話,如果你對NSURLSession不熟悉的話,那麼Alamofire源碼看起來會比較費勁的。在本篇部落格的第一部分我們先整體的概覽一下NSURLSession,以便後面一步步的展開。

廢話少說,進入本篇部落格的主題。從NSURLSession這個名字中我們不難看出,主要是URL + Session。顧名思義,NSURLSession是用來URL會話的。當然如果你做過伺服器端的開發,比如PHP,也會有Session的概念,不過此Session非彼Session,兩者的差別還是不小的。iOS的NSURLSession的主要功能是通過URL與伺服器履歷會話的。“會話”進一步說就是交流呗,一句話總結:也就是我們的iOS用戶端可以使用NSURLSession這個東西通過相應的URL與我們的伺服器建立會話,然後通過此會話來完成一些互動任務(NSURLSessionTask)。Session有着不同的類型,每種類型的Session又可以執行不同類型的任務(Task)。接下來就來介紹一下Session的類型以及所執行的任務等。

1.NSURLSession的類型

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

  • 預設會話(Default Sessions)使用了持久的磁盤緩存,并且将證書存入使用者的鑰匙串中。
  • 臨時會話(Ephemeral Session)沒有像磁盤中存入任何資料,與該會話相關的證書、緩存等都會存在RAM中。是以當你的App臨時會話無效時,證書以及緩存等資料就會被清除掉。
  • 背景會話(Background sessions)除了使用一個單獨的線程來處理會話之外,與預設會話類似。不過要使用背景會話要有一些限制條件,比如會話必須提供事件傳遞的代理方法、隻有HTTP和HTTPS協定支援背景會話、總是伴随着重定向。僅僅在上傳檔案時才支援背景會話,當你上傳二進制對象或者資料流時是不支援背景會話的。當App進入背景時,背景傳輸就會被初始化。(需要注意的是iOS8和OS X 10.10之前的版本中背景會話是不支援資料任務(data task)的)。

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

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

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會在下方的執行個體中進行一一的介紹,本部分就做一個概述。

二、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中指定了以下字元為保留字元:! * ' ( ) ; : @ & = + $ , / ? # [ ]。

在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編碼。

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

2.将Dictionary進行URL編碼

在iOS開發中,有時候我們從VC層或者VM層擷取到的資料是一個字典,字典中存儲的就是要發給伺服器的資料參數。直接将字典轉成二進制資料發送給伺服器,伺服器那邊是沒法解析iOS這邊的字典的,得有一個統一的互動标準,這個标準就是URL編碼。我們要做的就是講字典進行URL編碼,然後将編碼後的東西在傳給伺服器,這樣一來伺服器那邊就能解析到我們請求的參數了。下方折疊的這段代碼就是從AlamoFire架構中摘抄出來的三個方法,位于ParameterEncoding.swift檔案中。該段代碼就是負責将字典類型的參數進行URL編碼的,在編碼過程中進行轉義是少不了的。

iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶
iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶
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()方法就負責進行遞歸調用的。從下方的截圖中我們不難看出,字典、數組以及鍵值對的處理方式是不同的。

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

調用上述代碼段的query方法就可以對字典進行轉義。query()方法的參數是一個[String, AnyObject]類型的字典,傳回參數是一個字元串。這個傳回的字元串就是将該字典進行編碼後的結果。接下來我們對其進行測試。點選“URL編碼”按鈕就會執行下方的方法,在該方法中我們定義了一個字典,該字典的key是String類型的,Value中存儲的有String、Array以及Dictionary。将該字典作為參數傳入query()中,然後query()函數傳回的字元串進行資料。緊跟着的就是輸出結果,從結果中我們能看出将中文字元進行了百分号編碼,也就是URL編碼。

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

我們可以将上述輸出的字元串使用站上工具進行URL解碼,解碼後的URL如下所示:

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

三、資料任務--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方法進行喚醒。具體如下所示。
iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶

2. 測試

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

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

緊接着我們進行POST測試,也就是傳入"POST"已經相應的參數,具體如下所示。下方的輸出是伺服器傳回的資料。

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

四、上傳任務---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,當然不要忘了将任務進行喚醒。
iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶

接下來我們要将上述代碼進行測試,上面有兩測試位址,第一個是你可以使用的,第二個是我在我本地伺服器自己使用php寫的一個檔案上傳的腳本,當然你是使用不了的。如果你要運作上述代碼的話,你就要使用第一個位址進行測試了。下方代碼段就是我們的測試用例,首先我們先通過網絡擷取圖檔,并NSData加載到本地,擷取到圖檔的二進制資料imageData。等待圖檔資料擷取完畢後,在調用上述上傳資料的方法。為了請求完圖檔的二進制資料後在調用上述方法,我們使用了GCD中dispatch group的相關東西。關于GCD更為詳細的内容請參見之前的部落格《GCD詳解》。下方的代碼會在點選“UploadTask”按鈕時會被觸發。

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

在上傳檔案時,如果你想時刻的監聽上傳的進度,你可以去實作NSURLSessionTaskDelegate中的didSendBodyData方法,該方法會實時的監聽檔案上傳的速度。bytesSent回調參數表示本次上傳的位元組數,totalBytesSend回調參數表示已經上傳的資料大小,

totalBytesExpectedToSend表示檔案公有的大小。該回調方法具體實作方式如下,在下方回調方法中我們根據每次上傳的資料情況對進度條進行更新,當然在更新UI時我們要在主線程中進行更新。具體代碼如下。

 

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

五、下載下傳任務--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()呢。點選“開始下載下傳”的代碼如下所示。

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

3.暫停下載下傳

上面是開始下載下傳,接下來讓我們來實作暫停下載下傳。下方代碼段就是點選“暫停下載下傳”按鈕所觸發的方法。在該方法中我們主要調用了downloadTask中的cancelByProducingResumeData()方法來進行任務的暫停的。在調用上述方法時會通過Closure回調的形式傳回一個ResumeData,此處的ResumeData就是上面我們使用到的ResumeData。拿到該ResumeData後你要講它進行磁盤的持久化存儲,便于下次繼續下載下傳。此處為了友善就将ResumeData存到了UserDefault中,其實也就是plist檔案中。

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

下方就是我們在暫停下載下傳任務時所列印的ResumeData中的内容。從下方的内容不難看出ResumeData就是一個xml格式的文本資訊其中存儲着相應的下載下傳資訊。比如下載下傳檔案的URL(NSURLSessionDownloadURL),已經接受的資料位元組(NSURLSessionResumeBytesReceived)等等相關的下載下傳資訊,具體請看下圖。我們可以通過下方的xml存儲的資訊重新接着上次的下載下傳任務進行下載下傳。

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

上面有一項存儲的就是所下載下傳檔案的臨時檔案的名稱,就位于temp目錄中。在下載下傳過程中正在下載下傳的任務會在temp目錄中建立一個.tmp的臨時檔案用來存儲下載下傳的臨時資料,也就是說這個臨時檔案就是邊下邊存的地方。下載下傳完成後我們要對該臨時檔案進行轉存的,因為下載下傳完成後該臨時檔案會被自動删除的。

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

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

上面兩段代碼主要是用于下載下傳任務的開始和暫停的,如果你要想對下載下傳完成後的檔案進行處理,以及要監聽下載下傳進度的話,就得實作NSURLSessionDownloadDelegate代理中相應的方法了。NSURLSessionDownloadDelegate中有3個代理方法,分别負責處理檔案下載下傳完成,監測下載下傳進度以及檔案暫停時的處理工作。接下來将要結合上述下載下傳任務的開始和暫停的代碼來探讨一下這三個代理方法。

(1)、檔案下載下傳完成後的回調----didFinishDownloadingToURL

下方代碼段中的代理方法就是在檔案下載下傳完成後要執行的回調方法。在下方的委托回調方法中有三個回調參數,第一個就是我們的downloadSession對象,第二個參數就是我們的downloadTask對象,第三個參數就是臨時檔案的下載下傳目錄。臨時檔案在下載下傳完成之後如果你不做任何處理的話,那麼就會被自動删除。下方代碼段在擷取臨時檔案路徑後将臨時檔案使用FileManager将臨時檔案存儲到相應的檔案夾中,新檔案的名字此處取的是目前時間的時間戳,如下所示。

iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶
(2)、監聽下載下傳任務----didWriteData

下方代碼片段是用來實時監聽下載下傳進度的回調方法,該方法中有5個回調參數。前兩個就不說了,重點在後三個。didWriteData參數是本次下載下傳的資料,機關為位元組,totalBytesWritter參數代表着已經下載下傳的資料總量,totalBytesExpectedToWrite是檔案的總量。通過上述三個參數我們不難計算出目前的下載下傳進度,可以在該委托回調方法中進行ProgressiView的更新。具體代碼如下所示

iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶
(3)暫停後再次啟動下載下傳任務的代理方法----didResumeAtOffset

下方回調方法會在暫停的下載下傳任務重新開機後會被調用。該代理回調方法中有四個回調參數,前兩個就不多說了,我們來看後兩個。fileOffset代表中已經下載下傳檔案的大小,expectedTotalBytes表示檔案的總大小,如下所示:

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

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

六、網絡緩存

網絡緩存在網絡請求中使用的還是蠻多的,尤其是加載一些H5頁面時經常會加一些緩存來提高使用者體驗。有時的一些資料也會進行緩存,你可将資料緩存到你的SQLite資料庫、PList檔案,或者直接使用NSURLSession相關的東西進行緩存。接下來要介紹的緩存方式就是網絡緩存,就是利用NSURLSession相關的類來實作網絡緩存。該緩存的過程不需要你去操作資料庫或者plist檔案,下方給出了四種不同的網絡緩存方式,無論是哪一種網絡緩存的方式隻是用法不一樣,本質上是一樣的,都是利用NSURLSession進行的網絡緩存。廢話少說,進入該部分的主題。

1.緩存政策概述

在配置網絡請求緩存時,有着不同的請求緩存政策。下方就是所有支援的網絡緩存政策:

  • UseProtocolCachePolicy -- 緩存存在就讀緩存,若不存在就請求伺服器
  • ReloadIgnoringLocalCacheData -- 忽略緩存,直接請求伺服器資料
  • ReturnCacheDataElseLoad -- 本地如有緩存就使用,忽略其有效性,無則請求伺服器
  • ReturnCacheDataDontLoad -- 直接加載本地緩存,沒有也不請求網絡 
  • ReloadIgnoringLocalAndRemoteCacheData -- 尚未實作
  • ReloadRevalidatingCacheData -- 尚未實作

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

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

2.使用NSMutableURLRequest指定緩存政策

接下來我們使用NSMutableURLRequest來指定緩存政策,在NSMutableURLRequest類的對象中有一個參數cachePolicy用來指定緩存政策的,隻需要将上述枚舉的緩存政策的枚舉值指派給cachePolicy即可。下方代碼段就是點選“Request設定緩存”按鈕所觸發的代碼,在下方代碼中我們使用DataTask對百度的網頁進行請求,将請求的資料使用.ReturnCacheDataElseLoad的緩存政策進行緩存。下方紅框的部分就是使用NSMutableURLRequest對象來設定緩存政策的代碼,具體如下所示:

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

下方就是點選“Request設定緩存”按鈕後所呈現的效果,緩存目錄預設為~/Library/Caches/[Boundle ID]/fsCachedData/緩存檔案,緩存檔案名是按照一定的規則生成的,當然同一個URL所生成的緩存檔案名是相同的。下方就是我們所緩存的檔案,使用Sublime打開後裡邊就是百度的HTML頁面。如下所示。當緩存完畢後,如果你再次發起請求的話就會從緩存檔案中進行資料的加載。

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

3. 使用NSURLSessionConfiguration指定緩存政策

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

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

由于此處的緩存檔案與上述一緻,如果該請求連接配接以被上面緩存就會被直接加載。

4.使用URLCache + request進行緩存

上面是使用URLRequest自帶的緩存政策,可定制性和靈活度比較低。如果要對網絡緩存有着較高的定制性的話,我們就得使用NSURLCache這個東西了。雖然NSURLURLCache任然依賴于NSURLRequst對象,不過可以設定一些緩存的參,比如緩存路徑、緩存的最大磁盤容量和記憶體容量等等。接下來我們就要使用URLCache來進行網絡緩存了。下面的代碼就是對“部落格園”首頁的HTML進行的緩存,當然我們在此使用的是URLCache。

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

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

有一點需要注意的是此處設定的緩存路徑是相對于/Library/Caches/[Boundle ID]/的,會在這個相當路徑下建立相應的檔案夾來存放緩存檔案。下方就是我們使用NSURLCache緩存的檔案路徑已經内容,從内容不難看出就是部落格園首頁的HTML代碼。效果如下所示:

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

5、使用URLCache + NSURLSessionConfiguration進行緩存

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

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

6、清除緩存

誰污染誰治理呢,建立完緩存,如果在不用時我們要對相應的緩存資料進行清理的。清理緩存就是找到緩存所在的檔案夾将緩存的檔案進行删除即可。下方代碼段就是對我們上面建立的所有緩存進行清理。因為下方的每行代碼基本上都有注釋,在此就對其做過多的解釋了。主要還是NSFileManager的使用。如下所示:

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

七、請求認證

有時為了網絡請求的安全性,伺服器與用戶端之間要進行身份的驗證。根據安全性的不同要求可以是單向驗證,也可以是雙向驗證。本部分我們就來聊一下NSURLSession發起網絡請求遇到驗證時的處理方案,就以HTTPS證書驗證為例。下方會先介紹認證方式與認證政策,然後結合執行個體來進一步認識NSURLSession中的請求認證。

1.認證方式

首先我們先來大體的了解一下所有的認證方式

  • NSURLAuthenticationMethodHTTPBasic: HTTP基本認證,需要提供使用者名和密碼
  • NSURLAuthenticationMethodHTTPDigest: HTTP數字認證,與基本認證相似需要使用者名和密碼
  • NSURLAuthenticationMethodHTMLForm: HTML表單認證,需要提供使用者名和密碼
  • NSURLAuthenticationMethodNTLM: NTLM認證,NTLM(NT 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請求。

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

發起上述https請求後,就會執行下方的代理方法。下方的委托代理方法屬于NSURLSessionDelegate中處理認證的方法,也就是如果伺服器需要認證時就會執行下方的回調方法。下方代碼首先從授權質疑的保護空間中取出認證方式,然後根據不同的認證方式進行不同的處理。下方給出了兩種認證方式的處理,上面的if語句塊指派服務端認證,下面的if語句塊負責HTTP的基本認證。具體處理方式如下所示。有一點需要注意的是如果在該委托回調方法中如果不執行completionHandler閉包,那麼認證就會失效,是請求不到資料的。

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

八、NSURLSession相關代理

在AlamoFire架構中用到了好多的NSURLSession的相關代理,AlamoFire架構對NSURLSession的相關代理進行了封裝,使用Closure的形式進行了替換,是以在閱讀AlamoFire源碼之前了解NSURLSession的相關代理方法的功能比較重要的。接下來将要對NSURLSession所有相關的代理方法進行介紹,當然上面已經用到的代理方法在該部分就不重述了。下面的内容首先會整體的介紹一些這些代理的關系,然後各個擊破。

1.SessionDelegate類圖

下方類圖是SessionDelegate相關協定已經SessionTask相關類之間的繼承和依賴關系。上面已經介紹了各種Session Task的使用,當然除了Stream Task之外。Stream Task是iOS9之後添加的東西,用來進行資料流的請求與互動的,在此就不多說了。該部分是對下方類圖中上半部分進行介紹。Session相關的Delegate都繼承在NSURLSessionDelegate,DownloadDelegate、DataDelegate、StreamDelegate則繼承自SessionTaskDelegate。詳細的請看下方類圖。

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

2.Delegate測試用例

為了進行各種代理的測試,我們建立了下方專門用于代理測試的請求。網絡請求的位址使用的是“https://www.xinghuo365.com”,後面沒有加index.shtml。因為直接請求域名星火金服會進行重定向,正好在我們相應的代理方法中進行請求重定向的處理。點選“SessionDelegate”按鈕就會執行下方的方法。

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

3.NSURLSessionDelegate

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

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

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

4.NSURLSessionTaskDelegate

接下來我們來介紹NSURLSessionDelegate的子協定NSURLSessionTaskDelegate,當然父協定中的代理方法同樣适用于所有的子協定的。關于NSURLSessionTaskDelegate的代理方法,上面我們在介紹UploadTask時用到了NSURLSessionTaskDelegate協定中的

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

(1).請求的重定向

當我們請求的位址進行重定向時會執行NSURLSessionTaskDelegate中的willPerformHTTPRedirection方法,我們可以在此代理方法中對重定向的請求進一步的進行處理,甚至在此進行重定向。下方代碼段的截圖就是該URL重定向後要執行的方法,我們在此方法中将重定向的内容再次進行重定向,我們此處是重定向到的百度。具體做法如下所示。

iOS開發之Alamofire源碼解析前奏--NSURLSession全家桶
(2)、其他代理方法

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

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

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,就是允許繼續執行資料的請求和處理。

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

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

下方的代理方法就是在執行Data Task時,收到伺服器的資料後所執行的方法。也就是上面的處理政策設定成Allow後會執行下方的方法,如果響應處理政策不是Allow那麼就不會接收到伺服器的Data,進而也不會執行下面的方法。在該方法中我們收到了伺服器所傳回的二進制資料,下方我們将二進制資料轉成UTF8的字元串編碼。具體代碼如下所示:

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

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

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

   

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

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

如果你在執行Data Task時,如果指定了響應的緩存政策,那麼在請求資料完畢會會執行下方的willCacheResponse代理方法。顧名思義,willCacheResponse就是在将要進行緩存的使用多調用的,具體做法如下:

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

NSURLSessionStreamDelegate是iOS9上提供的,如果Data被轉換成流資料就會執行NSURLSessionStreamDelegate中相應的方法,在streamTask中有一個readDataOfMinLength方法可以讀取流中的資料。因為至今還用過NSURLSessionStreamDelegate,是以關于NSURLSessionStreamDelegate的東西就不做過多贅述了。不過在Github上分享的demo中有NSURLSessionStreamDelegate的相關内容,在此就不做過多的贅述了。

九、監測網絡連接配接狀态

本部分不屬于NSSession範疇,不過網絡開發怎麼能少的了監測網絡狀态的子產品呢。接下來我們将要使用SystemConfiguration來實作reachability。在AlamoFire中也是使用的SystemConfiguration相關的内容來實作的reachability。

下方這個代碼段就是使用SystemConfiguration相關的内容來進行網絡狀态的監測。首先我們先使用SCNetworkReachabilityCreateWithName來建立一個reachability對象,然後建立reachability的上下文,之後在設定網絡狀态改變後的回調,随後将reachability對象放到隊列中進行執行,具體步驟具體代碼如下所示:

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

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

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

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

上述是以代碼Github分享位址為:https://github.com/lizelu/NSURLSessionDemo

作者:青玉伏案

出處:http://www.cnblogs.com/ludashi/

本文版權歸作者和共部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

如果文中有什麼錯誤,歡迎指出。以免更多的人被誤導。

收履歷:某網際網路公司,招聘iOS/Android靠譜工程師,入職後,可内部聯系樓主,有小禮品贈送,有意者可郵箱投遞履歷:[email protected]