URL加载系统为请求的响应提供内存与磁盘复合的缓存。该缓存允许应用程序减少对网络连接的依赖,并增加它的性能。
为请求使用缓存
NSURLRequest实例通过设置缓存策略来指定本地缓存的使用方式。缓存策略可以通过为NSURLRequestCachePolicy设置以下值来得到:NSURLRequestUseProtocolCachePolicy, NSURLRequestReloadIgnoringCacheData, NSURLRequestReturnCacheDataElseLoad, 或 NSURLRequestReturnCacheDataDontLoad.
NSURLRequest实例的默认缓存策略是NSURLRequestUseProtocolCachePolicy。NSURLRequestUseProtocolCachePolicy行为是特定于协议的,并且被定义为协议最合适的策略。
将缓存策略设置为NSURLRequestReloadIgnoringCacheData,会导致URL加载系统从原始资源加载数据,而忽略以完成的缓存。
NSURLRequestReturnCacheDataElseLoad缓存策略会导致URL加载系统使用缓存数据,却忽略这个缓存是否陈旧。当且仅当没有缓存数据时,URL加载系统会从原始资源处加载数据。
NSURLRequestReturnCacheDataDontLoad策略允许应用程序指定缓存可以返回的唯一数据。尝试使用缓存机制创建一个NSURLSessionTask实例,如果响应不是在本地缓存中,立即返回nil。这在功能上和“离线”模式类似,并且不产生网络连接。
注意:当前,只有对HTTP和HTTPS请求的响应才会被缓存。FTP和file协议尝试访问缓存策略允许的原始资源。自定义NSURLProtocol类可以选择性的提供缓存。
缓存使用HTTP协议语义
最复杂的缓存使用情况,是当请求使用HTTP协议,并且设置NSURLRequestUseProtocolCachePolicy为缓存策略。
如果请求没有NSCachedURLResponse,则URL加载系统会从原始资源获取数据。
如果请求存在缓存的响应,URL加载系统会检查该响应,以确定是否它所指定的内容必须要重新验证。
如果该内容需要重新验证,URL加载系统会制作原始资源的HEAD请求,用来查看该资源是否已经改变。如果它没有改变,URL加载系统就返回缓存的响应。如果它已经改变,URL加载系统会从原始资源处获取数据。
如果缓存的响应没有指定内容必须重新验证,URL加载系统检查指定缓存的响应的有效期。如果缓存的响应是近期的,则URL加载系统返回缓存的响应。如果响应过期了,URL加载系统会制作原始资源的HEAD请求,用来查看该资源是否已经改变。如果它没有改变,URL加载系统就返回缓存的响应。否则它会从原始资源处获取数据。
RFC 2616第13节(http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13)
详细说明了涉及的语义。
以编码方式控制缓存
默认情况下,请求的数据会基于请求的缓存策略被缓存起来,由处理该请求的NSURLProtocol子类解读(interpreted)。
如果app需要对缓存进行更多精确的编程控制(如果该协议支持缓存),你可以实现委托方法,该方法允许app根据每个请求确定是否缓存特定的响应。
对于NSURLSession数据和上传任务,请实现URLSession:dataTask:willCacheResponse:completionHandler:方法。该委托方法只有在数据和上传任务的时候被调用。下载任务的缓存策略由特定的缓存策略决定。
对于NSURLSession,你的委托方法通过调用完成处理程序代码块告诉会话要缓存的内容。委托通常提供下列值中的一个:
- 提供响应对象来允许缓存
- 最新创见的响应对象来缓存修改的响应——例如,使用存储策略的响应允许缓存到内存而不是磁盘
- nil用来防止缓存
你的委托方法还能向与NSCachedURLResponse对象相关的userInfo字典插入对象,从而将这些对象作为响应的一部分存储到缓存中。
重要:你的委托方法必须始终调用完成处理程序。否则,app会泄露内存。
代码清单5-1的示例阻止HTTPS响应的磁盘缓存。它还把当前日期添加到响应的缓存的用户信息字典中。
代码清单 5-1 URLSession:dataTask:willCacheResponse:completionHandler: 实现示例
- (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);
}