URLCache
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print( NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true))
let urlCache = URLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil)
URLCache.shared = urlCache // 不赋值,也有默认的 URLCache
return true
}
1.
首先给
URLCache.shared 设置一个默认值
-
NSURLRequestUseProtocolCachePolicy
: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。 -
NSURLRequestReloadIgnoringLocalCacheData
:数据需要从原始地址加载。不使用现有缓存。 -
NSURLRequestReloadIgnoringLocalAndRemoteCacheData
:不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。 -
NSURLRequestReturnCacheDataElseLoad
:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。 -
NSURLRequestReturnCacheDataDontLoad
:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。 -
NSURLRequestReloadRevalidatingCacheData
:从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
)
/**
向代理询问数据(或上传)任务是否应将响应存储在缓存中。
在任务完成接收所有预期数据后,会话调用此委托方法。如果不实现此方法,则默认行为是使用会话配置对象中指定的缓存策略。此方法的主要目的是防止缓存特定的URL或修改与URL响应相关联的userInfo字典。
仅当处理请求的URLProtocol决定缓存响应时才调用此方法。通常,只有当以下所有条件都为真时,才会缓存响应:
请求是针对HTTP或HTTPS URL(或您自己的支持缓存的自定义网络协议)。
请求已成功(状态码位于200-299范围内)。
提供的响应来自服务器,而不是从缓存中。
会话配置的缓存策略允许缓存。
提供的NSURLRequest对象的缓存策略(如果适用)允许缓存。
服务器响应中与缓存相关的头(如果存在)允许缓存。
响应大小足够小以合理地适合缓存。 (例如,如果提供磁盘缓存,则响应不得大于磁盘缓存大小的大约5%)。
**/
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
let cacheResponse = CachedURLResponse(response: proposedResponse.response.copy() as! URLResponse, data: proposedResponse.data, userInfo: proposedResponse.userInfo, storagePolicy: .allowed)
completionHandler(cacheResponse)
}
) 我们着重看一下默认缓存策略UseProtocolCachePolicy
和忽略缓存的策略ReloadIgnoringLocalCacheData
,
当使用默认的缓存策略时:
第一次请求一个URL时,会将response和数据缓存起来,
再次请求相同的URL时,会使用缓存中的Etag作为这次请求的request的'If-None-Match'值,这样服务端会返回304并且response的数据体为空,此时iOS会帮助读取缓存中的数据体,修改次请求的response,将HTTP状态码改为200,使用修改后的response和缓存中取到的data作为参数执行完成回调。
以上过程看起来似乎很完美,除了状态码不是304,其他的过程和浏览器几乎一致。但是他有一个缺陷,在研究这个缺陷之前我们先弄清一个这么一个事实:请求内容可以分为三种 1.脚本2.用数据渲染的页面3.静态文件。
对于脚本请求的处理,服务端是会忽略Etag,而每次都会处理,这样返回的数据都是新的,返回HTTP状态码为200.
对于用数据渲染的页面,服务器会按照一定的计算规则,计算渲染之后的Etag,然后对比,再决定返回的是304或者200.
对于静态文件,有些服务器具有检测静态文件改变的能力,一旦文件发生改变,服务器会立刻检测到,从而返回200给客户端,而有些服务器检测文件改变的功能是有延迟的,或者根本没有这种功能,这样即使文件的内容改变了,服务器仍然认为没有改变,于是对比Etag依然相等,结果返回304.(这次测试使用了apache和Express,默认配置下的apache对文件改变的检测是有延迟的,Express则是实时检测的)
根据以上的描述就会暴露出使用默认缓存策略的一点劣势,如果服务器不能实时检测文件改变状态,那么文件是否改变的比对结果是不准确的。最糟糕的情况就是:当文件改变了,服务器认为仍然没有改变,从而返回了304,而没有携带最新的数据。
ReloadIgnoringLocalCacheData
策略时:
每次请求前都会忽略缓存,request的header从来不会附带'If-None-Match'值, 服务器每次处理成功后都是返回200,这样每次都会拿到服务器的数据(每次response的Date头都是新的值),服务器返回的response带有完整的数据体。iOS接收到数据之后,将response和数据缓存,并作为参数执行完成回调。
这里我们也能够看到使用ReloadIgnoringLocalCacheData
策略暴漏出来的缺点:尽管服务器端的文件确实没有改变,但iOS依然不使用本地已有的缓存,而每次服务端还要将数据发给客户端,这样是多么浪费带宽!
let url = URL(string: "http://c.3g.163.com/photo/api/list/0096/4GJ60096.json")
var request = URLRequest(url: url!)
//request.cachePolicy = .useProtocolCachePolicy // 默认缓存策略 test (没网的情况下)直接报错 、换句话说他要去向服务器请求资源是否改变的数据,而下方不需要
request.cachePolicy = .returnCacheDataElseLoad // test (没网的情况下) 直接会去取缓存 statusCode 被改为 200
let session = URLSession.shared //
let dataTask = session.dataTask(with: request) {(data , response , error) in
if let error = error {
print("error + \(error.localizedDescription)")
return
}
print("response state : \((response as! HTTPURLResponse).statusCode)")
if let data = data {
print( String(data: data, encoding: .utf8) ?? "data null")
}else{
print("data is nil")
}
}
dataTask.resume()