URL Loading System提供了对请求响应基于磁盘和内存缓存的综合方式。这些缓存可以减少程序对于网络连接的依赖,并提高程序的性能。
为请求使用缓存
NSURLRequest实例怎样使用缓存取决于如何设置缓存策略NSURLRequestCachePolicy,它是一个枚举类型,枚举值有:NSURLRequestUseProtocolCachePolicy, NSURLRequestReloadIgnoringCacheData, NSURLRequestReturnCacheDataElseLoad, 或者 NSURLRequestReturnCacheDataDontLoad.
NSURLRequest实例的默认缓存策略是NSURLRequestUseProtocolCachePolicy。NSURLRequestUseProtocolCachePolicy的相关行为是协议特定的,它被认为是最适合协议的缓存策略。
将缓存策略设置为NSURLRequestReloadIgnoringCacheData会导致URL loading system在加载源数据时会全部忽略缓存。
NSURLRequestReturnCacheDataElseLoad会让URL loading system在使用缓存数据时,忽略缓存可用有效期,只有在没有缓存时才会去发送源加载数据。
NSURLRequestReturnCacheDataDontLoad缓存策略让程序只返回缓存中指定的数据。当响应数据不在本地缓存中,此时尝试去创建一个NSURLSessionTask实例并使用这个缓存策略时,会立即返回nil值。
提示
目前只缓存了HTTP和HTTPS的请求,FTP和file协议尝试访问原始源所允许的缓存策略。自定义NSURLProtocol可以选择性地提供缓存。
| 缓存策略 | 意义 |
|---|---|
| NSURLRequestUseProtocolCachePolicy | 默认行为,最适合协议的缓存策略 |
| NSURLRequestReloadIgnoringCacheData | 不使用缓存数据 |
| NSURLRequestReturnCacheDataElseLoad | 当没有缓存时,去服务器拿源数据 |
| NSURLRequestReturnCacheDataDontLoad | 完全使用缓存数据,没有缓存则返回nil |
缓存使用HTTP的协议语义
使用缓存最复杂的情形就是在使用HTTP请求时设置该请求的缓存策略为NSURLRequestUseProtocolCachePolicy。
如果请求不存在NSCachedURLResponse,那么URL loading system会从数据始发源获取数据。
如果请求中存在响应缓存,URL loading system会检查响应的内容,以确定该内容是否需要重新验证。如果内容必须要重新验证,URL loading system给数据始发源发送一个HEAD请求来查看资源是否有更改。如果没有发生改变,URL loading system返回响应缓存;如果改变了,URL loading system从数据始发源的数据。
如果响应缓存没有指定需要重新验证的内容,URL loading system则检查指定缓存的有效期。如果响应缓存很接近有效期, URL loading system仍然返回响应缓存;如果响应缓存过期, URL loading system同样会发送一个HEAD请求去查看资源是否修改,如果修改则获取最新数据,如果没有修改则直接返回响应缓存。
RFC 2616, Section 13详细说明了所涉及的语义。这里制作了一个简单的流程图:

通过编程来控制缓存
默认情况下,数据请求的缓存是基于该请求的缓存策略的,并由处理该请求的NSURLProtocol子类来解析。如果我们在程序中需要对缓存进行更精确地控制(前提是协议得支持缓存),那么就需要我们实现相关的代理方法来决定应用程序是否需要对每个请求的特定响应数据进行缓存。
对于NSURLSession的数据和上传任务,实现URLSession:dataTask:willCacheResponse:completionHandler:方法。这个方法只会在数据任务和上传任务时被调用。下载任务需要指定具体的缓存策略。
对于NSURLSession,代理方法中需要调用completionHandler来告诉session什么内容需要去缓存。代理通常提供如下值:
- 允许提供的响应内容进行缓存;
- 新创建的响应对象去缓存被修改的响应内容——比如,只允许缓存到内存而不允许缓存到磁盘的存储策略;
- 防止缓存nil;
我们可以通过代理方法把对象插入到NSCachedURLResponse对象的userInfo字典中,让被插入的对象作为响应缓存的一部分。
⚠️ ⚠️ ⚠️ 特别注意
代理方中需要时常地去调用提供的completionHandler,否则会出现内存泄漏。
下面展示了HTTPS响应,并将其缓存到磁盘中。同时也把响应的数据添加到userinfo字典中缓存起来。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * __nullable cachedResponse))completionHandler {
NSCachedURLResponse *newCachedResponse = proposedResponse;
NSDictionary *newUserInfo;
newUserInfo = [NSDictionary dictionaryWithObject:[NSDate date]
forKey:@"Cached Date"];
if ([proposedResponse.response.URL.scheme isEqualToString:@"https"]) {
#if ALLOW_IN_MEMORY_CACHING
newCachedResponse = [[NSCachedURLResponse alloc]
initWithResponse:proposedResponse.response
data:proposedResponse.data
userInfo:newUserInfo
storagePolicy:NSURLCacheStorageAllowedInMemoryOnly];
#else // !ALLOW_IN_MEMORY_CACHING
newCachedResponse = nil;
#endif // ALLOW_IN_MEMORY_CACHING
} else {
newCachedResponse = [[NSCachedURLResponse alloc]
initWithResponse:[proposedResponse response]
data:[proposedResponse data]
userInfo:newUserInfo
storagePolicy:[proposedResponse storagePolicy]];
}
completionHandler(newCachedResponse);
}