Example 项目地址: github.com/zColdWater/… 下载Demo,结合Demo一块儿实践更容易理解。html
在开始以前,咱们须要清楚下面的一些问题,才方便咱们后面的讲解。git
咱们一提到URLRequest
,我相信不少国内的开发者,首先就会联想到,HTTP
请求,而后木有别的了。 可是其实 URLRequest
是一个很大的概念,它不仅服务于HTTP
协议,它还服务于 其余应用协议,好比File
协议,Data
协议,自定义协议等等。 要么苹果公司为何不叫它HTTPURLRequest
呢? 问题就在于咱们平时最常接触的就是HTTP
协议,用来请求服务端的数据用来展现。github
经过上面的文章咱们清楚了 URLRequest
服务不少协议,那么URLRequest.CachePolicy
的范围是什么呢,很明显,和URLRequest
同样,这个缓存策略也包含上面这些协议,固然 我也不清楚其余协议的缓存策略是什么样子的,好比File
协议,或则别的。 可是我很清楚,咱们经常使用的HTTP
协议的缓存协议,这个后面再讲,这里清楚的是,这个缓存策略支持不少协议,咱们的HTTP
协议有着本身的缓存策略。web
我以前转了一篇台湾做者关于HTTP
协议的缓存策略的文章,文章地址是: http://47.99.237.180:8080/articles/2019/11/18/1574050998351.html 那么HTTP
协议的缓存策略是什么呢?swift
Note: 首先咱们须要清楚的是,
HTTP
协议的策略是须要客户端
和服务端
配合完成的。也就是若是这个策略想要完成,须要双方都有动做,而且客户端
须要彻底配合才行。浏览器
我用最直白的话来概述这个原理:缓存
首先客户端
第一次访问服务器
某一个资源,而且服务器
和客户端
协商好,咱们都用标准的HTTP
缓存协议。bash
由于是第一次,客户端
经过URL
来查找,发现本地没有缓存,直接向服务器发起一个HTTP
协议的网络请求。 客户端
的请求头,例如:服务器
GET /EC6/poster-share-invite.png HTTP/1.1
Host: fep-sit.nioint.com:5418
Accept: */*
User-Agent: SessionDownload/1 CFNetwork/1107.1 Darwin/19.0.0
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
复制代码
这其实就是一个普通的网络请求,请求头也是客户端默认的,没有特殊设置。网络
服务器
接到了某一个客户端
的发来的请求,而后作的直接就看一下这个请求头有没有提供HTTP
协议缓存的相关字段,而后根据HTTP
协议缓存规则,来判断是否返回状态码304
让客户端用缓存,仍是返回状态码200
,让客户端使用服务器返回的新资源,服务器检查请求头的相关字段以下:
If-None-Match
: W/"20f9a-16f5c76370d" 这个字段你能够理解为这个资源的惟一Hash值,有点像MD5或者SHA1等,反正就是一个惟一标识啦,资源若是有变更,它必定就会有变更,而且这个值是从上一次服务器
返回的响应头里面的Etag
字段取来的。由于咱们客户端是第一次请求,因此没有从以前的服务器响应里面拿到这个值,因此请求头就没有这个字段。If-Modified-Since
:Tue, 31 Dec 2019 14:57:28 GMT,这个字段表示的是最后一次资源更改的时间,同If-None-Match
也是从上一次的服务器响应头中拿到,从Last-Modified
字段取的。由于第一次请求,因此没有获取到上一次响应头的字段,也就没有带上。服务器开始根据HTTP
协议规则进行检查,来决定是让客户端使用缓存仍是使用服务器下发的资源。
HTTP/1.1 200 OK
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
ETag: W/"20f9a-16f5c76370d"
Content-Type: image/gif
Content-Length: 135066
Date: Wed, 01 Jan 2020 01:56:35 GMT
Proxy-Connection: keep-alive
复制代码
这里注意的是,在iOS当中,你不须要亲自处理
304
的状况,若是你使用了默认的缓存策略,也就是使用HTTP协议自己的缓存策略,系统的网络框架好比URLSession
或者URLConnection
会自动的将这个304
处理成200
,这样方便了开发者逻辑处理,开发者只须要知道 资源获取成功,就能够了。
HTTP/1.1 304 Not Modified
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
ETag: W/"20f9a-16f5c76370d"
Date: Wed, 01 Jan 2020 01:59:25 GMT
Proxy-Connection: keep-alive
复制代码
Cache-Control
: max-age = X (max-age=x 是告诉客户端x秒以内不要再发起请求了,就用你的缓存就OK了,换句话说,若是服务器告诉客户端max-age=100,客户端在100s以内再去请求,是不会发起真正的网络请求的,客户端的网络层框架会自动返回状态码200,上一次的缓存数据)Last-Modified
: Tue, 31 Dec 2019 14:57:28 GMT(这个字段是告诉客户端,这个资源最后一次更新的时间,让客户端保存好,下一次请求的时候,在请求头里面带上这个值,请求头的那个字段就是If-Modified-Since
,这里的规则是这样的,若是请求头里的If-Modified-Since
时间点早于服务器的Last-Modified
时间点,服务器会返回200,让客户端须要更新最新资源,若是反过来,或者相同,服务器会下发304,让客户端使用缓存。)ETag
: W/"20f9a-16f5c76370d"(这个字段告诉客户端,这个值是这个资源的惟一id,若是服务器上面有新资源,咱们会更新这个值,客户端要保存好,下次请求的时候带上,下次请求头里面这个If-None-Match
字段就是保存的上次响应头里面的Etag
字段。它的规则是,若是客户端请求头中的If-None-Match
值不与服务器里面的Etag
一致,就返回200,让客户端使用新资源,只有当相等的状况,会返回304,让客户端使用缓存。)服务器检查完请求头发现这个客户端没有带上资源缓存信息,那么服务器就认为客户端不想使用HTTP
协议缓存策略,返回200,把资源也一同返回。
HTTP/1.1 200 OK
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
ETag: W/"20f9a-16f5c76370d"
Content-Type: image/gif
Content-Length: 135066
Date: Wed, 01 Jan 2020 01:56:35 GMT
Proxy-Connection: keep-alive
复制代码
客户端第一次拿到资源后,先将服务器告诉本身的缓存信息,保存起来,Cache-Control: public, max-age=0
读取一下max-age
,发现值等于0,这是告诉我应该每次都发真正的请求,而后再存一下Last-Modified
上次更新的时间,再存一下ETag
,这个告诉咱们资源的惟一id。
而后客户端
开始发起第二次请求,请求头以下:
GET /demo.gif HTTP/1.1
Host: 152.136.154.126:3000
If-None-Match: W/"20f9a-16f5c76370d"
Accept: */*
If-Modified-Since: Tue, 31 Dec 2019 14:57:28 GMT
User-Agent: SessionDownload/1 CFNetwork/1107.1 Darwin/19.0.0
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
复制代码
能够看到,客户端将缓存资源的信息带上来了,在If-None-Match
,If-Modified-Since
。
服务器
再次接到这个请求头,和本身的对资源的信息对比,发现If-None-Match
和ETag
一致的,发现Last-Modified
和If-Modified-Since
一致的,那么客户端的资源就和服务器是一致的,我应该告诉客户端304状态码,让客户端使用缓存就能够了。 响应头以下:HTTP/1.1 304 Not Modified
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
ETag: W/"20f9a-16f5c76370d"
Date: Wed, 01 Jan 2020 03:02:57 GMT
Proxy-Connection: keep-alive
复制代码
自此,一次完整的HTTP协议缓存策略应用完成,咱们能够看到,客户端第一次发起请求,第二次发起请求,请求头里面的变化。和服务器如何对请求头作出的校验和响应。
我知道还有
Expire
字段,也是缓存相关的,可是HTTP1.1以后设置Cache-Control里面的max-age,能够覆盖它,若是他们同时存在,因此这里再也不概述,若是你感兴趣,能够WIKI,或者 http://47.99.237.180:8080/articles/2019/11/18/1574050998351.html
但愿我能够说清楚这个过程,由于下面iOS下面的网络框架就会涉及到。 这个是HTTP协议的缓存策略,意味着,只要使用HTTP协议的客户端,不论是浏览器,仍是移动端,仍是其余,都使用。
即便如今不是特别清楚也不要紧,我会在文章中附上,整个过程的Demo,能够运行Demo,来了解每一步。
在验证过程当中除了一些小插曲,好比我已经为服务器的静态资源,设置资源的过时时间,在响应头当中,
Cache-Control: max-age=xxx
,发如今移动端上,是OK的,在xxx秒以内再次访问,真的没有发起网络请求,可是在Chrome
当中我发现直接输入资源地址,它好像会忽略过时时间同样,都会发起真正的网络请求,我看了小半天,我终于发现了,这个问题出在哪了,下面我来说一下。
标签
或者XHR
发起的网络请求,设置超时时间工做正常。总结下来: 只有 3
显示的不正常,问题可能就是由于直接在浏览器里面输入资源地址去访问资源,忽略超时时间,可能因为某些缘由吧,因此每次才都会发起网络请求,并且Chrome和Safri的表现行为还不同,因此咱们若是想验证HTTP协议的缓存策略其实能够忽略掉3
,验证1
和2
便可。
上面咱们主要介绍了一些事先须要了解的知识,好比iOS里面的URLRequest和URLRequest.CachePolicy使用范围,还有最重要,也是最复杂的HTTP
协议缓存策略。
CachePolicy
:顾名思义这个是缓存策略的意思,我下面会仔细说它,这里咱们知道它表明一种缓存的策略就OK。
URLCache
: 这个其实就是咱们缓存的对象,也就是咱们使用了某种缓存策略,把内容存储的地方和配置存储在哪地方,等等,或者说管理URL缓存的对象。
小结: CachePolicy
用于选择一种缓存策略,URLCache
管理和设置缓存对象。
首先先了解 CachePolicy
有哪些策略可选:
useProtocolCachePolicy
// 使用协议缓存,好比你的URL
是HTTP
协议的,那就是使用HTTP
协议的缓存策略,就是我上面讲的,根据请求和响应头的关键字,进行缓存的处理。 这也是(默认的策略)。reloadIgnoringLocalCacheData
// 彻底忽略本地缓存,直接从服务器拿资源。reloadIgnoringLocalAndRemoteCacheData
// 这个实例是未实现的,你不该该使用reloadIgnoringCacheData
// 这个选项已经被 reloadIgnoringLocalCacheData 选项所替代了。returnCacheDataElseLoad
// 若是本地有缓存就使用缓存,无论HTTP协议上缓存上的那些max-age,expire 过时时间等,没有缓存再去远端请求拿数据。这个协议存在的问题就是,若是使用HTTP协议的缓存策略,服务器没办法告诉它,它手头的这份资料是旧的。returnCacheDataDontLoad
// 若是本地有缓存就使用缓存,本地没有缓存就请求失败,相似离线模式。reloadRevalidatingCacheData
// 这个实例是未实现的,你不该该使用它。其实苹果给的默认策略,一点毛病都没有,根据HTTP协议的缓存策略来配置是最好的,有缓存使用缓存,没有缓存,或者缓存过时,来请求服务器的资源。 也就是你根本毛都不用去设置,可是咱们常常会被问及,为啥我服务器换了图片,你移动端iOS不更新呢,若是你是默认策略,我能够很负责的告诉你,你能够狠狠的怼回去,你懂不懂HTTP协议的缓存策略,我这是官方的实现,而不是,不明道理,萎萎诺诺,好,我改为一切都从服务器获取。 不懂的哥们还觉得你技术不过关呢,不过说句实话,确实许多人真的不太清楚
HTTP协议
的缓存策略。
有哪些地方能够设置缓存策略URLRequestCache
呢? 其实看名字你就应该知道,这东西确定是给URLRequest
设置的,我每个网络请求,都会有一个URLRequest
,可是,为了方便,苹果会有两个地方给你设置这个策略的地方,分别是:
URLSession
发出去的URLRequest
都会带上这个策略。let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
var request = URLRequest(url: url)
let condig = URLSessionConfiguration.default
// 这 这 这 ⬇️ 这 这 这 ⬇️ 这 这 这 ⬇️ 这 这 这 ⬇️
condig.requestCachePolicy = .reloadIgnoringLocalCacheData
let session = URLSession(configuration: condig)
复制代码
URLRequest
的缓存策略,这个就没什么说的了。let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
var request = URLRequest(url: url)
// 这 这 这 ⬇️ 这 这 这 ⬇️ 这 这 这 ⬇️ 这 这 这 ⬇️
request.cachePolicy = .returnCacheDataDontLoad
let condig = URLSessionConfiguration.default
let session = URLSession(configuration: condig)
复制代码
Q 疑问🤔️? 同时设置,URLSessionCondiguration
和 URLRequest
的缓存策略,系统到底该用哪一个呢?
A 答案: URLRequest
的缓存策略会覆盖掉 URLSessionCondiguration
的缓存策略。
区分
URLCache
和NSCache
nshipster.com/nsurlcache/ 这俩东西不是一回事哈,看名字你也知道了,一个是专门缓存URL的类,一个是相似Map,字典的存储结构,用来缓存内存对象的。
上面也说了,URLCache
这个对象,实际上是用于管理URL的缓存的,好比放在哪里呀,大小设置多少呀。
都哪些地方能够设置呢? 有俩地方能够设置,分别是:
URLSessionConfiguration: 单独为这个URLSession
配置缓存对象,大小,路径等,若是单独为URLSessionConfiguration
配置了缓存对象,由这个URLSession
发出去的URLRequest
,都会使用这个缓存配置,不会使用全局的缓存对象。
let condig = URLSessionConfiguration.default
condig.requestCachePolicy = .reloadIgnoringLocalCacheData
let cache = URLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024)
condig.urlCache = cache
复制代码
URLCache: 类方法提供了全局的URLCache
配置,当URLSessionCondiguration
没有另外设置的状况下,会使用全局的缓存做为本身的缓存。
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
复制代码
Q 疑问🤔️? 若是同时设置全局缓存
和URLSessionCondiguration
缓存,系统有哪一个缓存?
A 回答: 确定使用URLSessionCondiguration
的缓存,忽略全局缓存。
URLSessionConfiguration 的缓存对象,空间设置为0。
let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
var request = URLRequest(url: url)
let condig = URLSessionConfiguration.default
// 为这个Session单独设置缓存,而且空间都为0,由于都是用默认缓存策略,也就是使用`HTTP协议`缓存,可是本地缓存没有设置空间,因此每次都会从服务器拿去最新的资料。
let cache = URLCache(memoryCapacity: 0, diskCapacity: 0)
let session = URLSession(configuration: condig)
let task = session.dataTask(with: request) { (data, resp, error) in
let httpResp = resp as! HTTPURLResponse
print("response (StatusCode):\(httpResp.statusCode)")
DispatchQueue.main.async {
let httpResponse = resp! as! HTTPURLResponse
for (key,value) in httpResponse.allHeaderFields {
print("\(key):\(value)")
}
self.imageView.image = UIImage.gifImageWithData(data: data! as NSData)
}
}
task.resume()
复制代码
URLCache全局缓存对象,空间设置为0。
// 由于URLSessionCondiguration的缓存没有特殊设置,因此使用全局缓存,又由于全局缓存空间设置成0,又由于是默认缓存策略,因此每次都从服务器去拿最新资料。
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
var request = URLRequest(url: url)
let condig = URLSessionConfiguration.default
let session = URLSession(configuration: condig)
let task = session.dataTask(with: request) { (data, resp, error) in
let httpResp = resp as! HTTPURLResponse
print("response (StatusCode):\(httpResp.statusCode)")
DispatchQueue.main.async {
let httpResponse = resp! as! HTTPURLResponse
for (key,value) in httpResponse.allHeaderFields {
print("\(key):\(value)")
}
self.imageView.image = UIImage.gifImageWithData(data: data! as NSData)
}
}
task.resume()
复制代码
关于WebView
这块是这样的,你设置的缓存策略,是页面里面的标签的缓存策略,也就是,你虽然设置了缓存策略是: 不使用本地缓存,只是HTML加载的那些标签使用这个策略,好比<img>
,可是若是在HTML的JS发起的XHR,或者Fetch请求,使用的仍是 默认缓存策略,你不会改变到他们。 这是我用网络拦截验证到的结果,Demo在文章开头和结尾都有。
拦截工具 项目地址: github.com/zColdWater/… 。
一,若是你想让这个网站的标签都使用默认的缓存策略,你就不须要另外设置。 也就是使用HTTP协议的缓存策略。
let url = URL(string: "https://www.baidu.com/")
var request = URLRequest(url: url!)
webview.load(request)
复制代码
二,若是你想让这个网站的资源标签
使用特定的缓存策略来请求,你能够这样处理。
let url = URL(string: "https://www.baidu.com/")
let request = URLRequest(url: self.url!, cachePolicy: .reloadIgnoringLocalCacheData)
webview.load(request)
复制代码
小结: 若是文章不够直观,能够下载Example项目,运行查看哦。
其实对于iOS当中的URLRequest
缓存,苹果的默认策略就是很是合理的选择,可是想要合理的使用这个默认策略,你须要了解HTTP协议
的缓存策略,对于WebView
的缓存策略咱们也说明了,我但愿能够帮到以前对这个缓存策略不清楚的童鞋,不要遇到问题一股脑就设置成不使用缓存了。 合理的使用缓存是最好的选择。
但愿我能够说的清楚明白,若有不许确或者不对的地方,但愿你们指正。
Example 项目地址: github.com/zColdWater/… 下载Demo,结合Demo一块儿实践更容易理解。