絕大多數移動客戶端在設計網絡模塊時,都會選用HTTP做爲客戶端和服務端通訊的網絡協議。隨着業務的不斷髮展以及用戶量的持續增加,整個客戶端的穩定性和性能會逐漸成爲關注的焦點,其中網絡的性能優化更是重中之重,本文介紹的 ETag 緩存技術,能夠在緩存數據的同時作到數據的實時更新,適用於對數據實效性要求較高的業務。html
相同的兩次請求返回的結果相同時,第一次返回的結果緩存在客戶端,第二次服務端再也不返回結果,僅返回一個特殊的狀態碼,告訴客戶端第二次請求的結果與上次相同,能夠直接使用上次返回的數據。swift
實現中,會用到HTTP頭中的兩個字段:緩存
ETag 返回應答數據的標記,服務端生成發送給客戶端性能優化
If-None-Match 一樣的請求,上一次返回的 ETag 值服務器
服務端在將數據發送給客戶端以前,首先計算應答數據的摘要(一般是MD5),把計算結果做爲 ETag 的值,和數據一同發送給客戶端。網絡
客戶端收到應答數據後,檢測 HTTP Header 中是否有 ETag 字段,若有則緩存應答數據和 ETag 的值。session
客戶端再次發起同一請求時,讀取上次緩存的 ETag 值,將其做爲 If-None-Match 的值,並與請求數據一同發送給服務端。性能
服務端收到請求,執行請求,在把應答數據返回給客戶端以前計算摘要,並與客戶端上報的摘要比較,若是兩次摘要相同,說明本次的應答數據與上一次請求的應答數據相同,且客戶端已緩存該數據,則簡單返回304。優化
客戶端收到304,直接讀取本地緩存的數據返回給調用網絡模塊的業務方。url
交互過程總結以下圖:
本文的示例代碼使用 NSURLSession
實現,因爲 NSURLSession
完善的緩存策略,爲了演示 ETag 的用法,須要先關閉緩存。
let config = NSURLSessionConfiguration.defaultSessionConfiguration() config.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData let session = NSURLSession(configuration: config)
NSURLSessionConfiguration
定義了 NSURLSession
在上傳和下載時的行爲及策略,咱們指定了請求的緩存策略爲 ReloadIgnoringCacheData
, 意思就是在請求時不使用本地緩存。
建立請求的 NSURLRequest
對象:
let url = NSURL(string: "http://www.joywek.com/50x.html")! let request = NSMutableURLRequest(URL: url)
上面會下載指定靜態頁面的內容,這個頁面是放在 Nginx 的服務器上,Nginx 默認會對應答數據計算 ETag。固然,在實際的應用中請求的都是動態數據,服務器要動態計算 ETag 的值。
設置 HTTP Header 中的 If-None-Match 字段:
if let tag = self.findTagByURL(url) { request.addValue(tag, forHTTPHeaderField: "If-None-Match") }
在發起請求時,先檢查相同的請求是否存在 ETag,若是存在就,就意味着上次請求的應答數據已緩存。
發起請求並處理應答數據:
self.dataTask = session.dataTaskWithRequest(request, completionHandler: { (var data, response, error) -> Void in data = self.handleResponse(response!, data!, request) }) self.dataTask?.resume()
若是 HTTP 返回的狀態碼是 200,說明是服務器正常返回數據,此時記錄 ETag 的值並緩存應答數據:
if (resp.statusCode == 200) { self.etags[response.URL!] = resp.allHeaderFields["ETag"] as? String let cachedResponse = NSCachedURLResponse(response: resp, data: data) NSURLCache.sharedURLCache().storeCachedResponse(cachedResponse, forRequest: request) return data }
若是返回 304,說明應答數據沒有變化,與上次請求的同樣,則直接返回緩存中的數據:
else if (resp.statusCode == 304) { let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(request) return cachedResponse?.data }