iOS網路請求快取:NSURLCache詳解

Roxily發表於2016-12-11

我讀過一些開源專案的網路請求快取的程式碼,基本上都是採用在本地存檔案的方式進行快取。如果你打算在你的專案中加入網路請求的快取,可能你並不需要自己造一個輪子,瞭解一下NSURLCache就足夠。

這是一個Apple已經為你準備好了的網路請求快取類。網上對這個類的介紹並不多,並且有的文章講得很不詳細。希望這篇文章能讓你對NSURLCache有一個比較詳細的瞭解。

快取

首先,NSURLCache提供的是記憶體以及磁碟的綜合快取機制。許多文章談到,使用NSURLCache之前需要在AppDelegate中快取空間的設定:

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                       diskCapacity:20 * 1024 * 1024
                                                           diskPath:nil];
  [NSURLCache setSharedURLCache:URLCache];
}複製程式碼

然而如果你不新增上面的程式碼,並且執行如下程式碼,可以看到:

print(NSURLCache.sharedURLCache().diskCapacity)
//output:
//10000000

print(NSURLCache.sharedURLCache().memoryCapacity)
//output:
//512000複製程式碼

也就是說,其實預設就已經設定好了512kb的記憶體快取空間,以及10MB的磁碟快取空間。可能你的程式碼中並沒有寫任何與NSURLCache有關的東西,但其實它已經默默的開始幫你進行快取了。

已經快取上了,但是怎麼使用快取呢?請繼續往下。

快取策略

GET

不用多說,NSURLCache只會對你的GET請求進行快取。

NSURLRequestCachePolicy

NSURLRequest中有個屬性:

public var cachePolicy: NSURLRequestCachePolicy { get }複製程式碼

你可以通過這個屬性來設定請求的快取策略,

public enum NSURLRequestCachePolicy : UInt {

    case UseProtocolCachePolicy // 預設值

    case ReloadIgnoringLocalCacheData // 不使用快取資料
    case ReloadIgnoringLocalAndRemoteCacheData // Unimplemented
    public static var ReloadIgnoringCacheData: NSURLRequestCachePolicy { get }

    case ReturnCacheDataElseLoad // 無論快取是否過期都是用快取,沒有快取就進行網路請求
    case ReturnCacheDataDontLoad // 無論快取是否過期都是用快取,沒有快取也不會進行網路請求

    case ReloadRevalidatingCacheData // Unimplemented
}複製程式碼

其實其他幾個值都比較好理解,唯獨預設值UseProtocolCachePolicy讓我不太懂。

字面上的意思是按照協議的快取策略進行快取,那麼這是什麼協議呢?http協議

詳細:RFC 2616, Section 13

伺服器返回的響應頭中會有這樣的欄位:Cache-Control: max-age or Cache-Control: s- maxage,通過Cache-Control來指定快取策略,max-age來表示過期時間。根據這些欄位快取機制再採用如下策略:

  • 如果本地沒有快取資料,則進行網路請求。
  • 如果本地有快取,並且快取沒有失效,則使用快取。
  • 如果快取已經失效,則詢問伺服器資料是否改變,如果沒改變,依然使用快取,如果改變了則請求新資料。
  • 如果沒有指定是否失效,那麼系統將自己判斷快取是否失效。(通常認為是6-24小時的有效時間)

其實我以前對Cache-Control之類的也並不太瞭解 T_T,自己默默的print了一下響應頭,你可以看到:

print((response as? NSHTTPURLResponse)?.allHeaderFields)

//響應頭中:Cache-Control: no-cache複製程式碼

這也就是為什麼,雖然NSURLCache一直在默默的快取,但是我並沒有感受到,當然或許你那裡不一樣。這個no-cache就表示不快取。(勘誤) 修正:no-cache表示不使用快取,但是會快取,no-store表示是不進行快取。

這裡要額外提一句,看到網上有同學說自己出現了某個請求資料一直使用快取,沒有被更新。這種情況可能就是伺服器返回的Cache-Control有誤。

開啟沙盒路徑下的Library/Caches 中,你可以看到快取檔案:

iOS網路請求快取:NSURLCache詳解
沙盒中的快取檔案

這可以說明存在磁碟上的資料是存在資料庫裡的,效能不用擔心。開啟資料庫檔案就可以看到請求的資料。

iOS網路請求快取:NSURLCache詳解
快取資料

cfurl_cache_response表中可以看到有一個欄位是request_key,通過裡面的值可以推斷每一個response是通過請求的url+引數來作為key儲存的。

當然,經過我的多次試驗,在Cache-Control: no-cache的情況下,NSURLCache也會進行快取,但是並不使用快取資料。

總結一下:預設情況下NSURLCache的快取策略是根據http協議來的,伺服器通過Cache-Control: max-age欄位來告訴NSURLCache是否需要快取資料。

快取封裝

如果你不打算採用http協議的快取策略,依然可以使用NSURLCache進行快取。

public func cachedResponseForRequest(request: NSURLRequest) -> NSCachedURLResponse?複製程式碼

你可以通過這個方法,傳入請求,來獲取快取。NSCachedURLResponse儲存了上次請求的資料以及響應頭。

public func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest request: NSURLRequest)複製程式碼

NSURLSessionDelegate協議中有如下方法,可以對即將快取的資料進行修改,新增userInfo,在代理方法中必須呼叫completionHandler,傳入將要快取的資料,如果傳nil則表示不快取。

optional public func URLSession(session: NSURLSession, 
                               dataTask: NSURLSessionDataTask, 
     willCacheResponse proposedResponse: NSCachedURLResponse, 
                      completionHandler: (NSCachedURLResponse?) -> Void)複製程式碼

Alamofire中可以這樣寫:

Alamofire.Manager
.sharedInstance
.delegate
.dataTaskWillCacheResponse = { (session, task, cachedResponse) -> NSCachedURLResponse? in
    var userInfo = [NSObject : AnyObject]()
    // 設定userInfo
    return NSCachedURLResponse(response: cachedResponse.response,
                               data: cachedResponse.data,
                               userInfo: userInfo,
                               storagePolicy: cachedResponse.storagePolicy)
}複製程式碼

#參考

我的blog:Roxily's Blog

簡書:Roxily

我也只是簡單的對NSURLCache進行了介紹,需要深入瞭解的話大家還是需要拜讀一下文章,希望能給大家一些幫助:

相關文章