基于NSURLCache的缓存实现

URL Loading System是类和协议的集合,使用URL Loading System iOS系统和服务器端进行网络交互。URL作为其中的核心,能够让app和资源进行轻松的交互。为了增强URL的功能Foundation提供了丰富的类集合,能够让你根据地址加载资源、上传资源到服务器、管理cookie、控制响应缓存(这也是本文重点)、处理证书和认证、扩展用户协议等,因此了解URL缓存之前熟悉URL Loading System是必要的。下图是这一系列集合的关系:

NSURLProtocol

URL Loading System默认支持http、https、ftp、file和data协议,但是它同样也支持注册自己的类来支持更多应用层网络协议。具体而言NSURLProtocl可以实现以下需求(包含但不限):

  • 重定向网络请求(或进行域名转化、拦截等,例如:netfox)

  • 忽略某些请求,使用本地缓存数据

  • 自定义网络请求的返回结果 (比如:GYHttpMocking)

  • 进行网络全局配置

NSURLProtocol类似中间人设计,将网络求细节提供给开发者,而又以一种优雅的方式暴漏出来。NSURLProtocol的定义更像是一个URL协议,尽管它继承自NSObject却不能直接使用,使用时需要自定义一个协议继承NSURLProto

解决DNS劫持

随着互联网的发展,运营商劫持这些年逐渐被大家所提及,常见的劫持包括HTTP劫持和DNS劫持。对于HTTP劫持更多的是篡改网络响应加入一些脚本广告之类的内容,解决这个问题只要使用https加密请求交互内容;而对于DNS劫持则更加可恶,在DNS解析时让请求重新定向到一个非预期IP从而达到内容篡改。

解决DNS劫持普遍的做法就是将URL从域名替换成IP,这么一来访问内容并不经过运营商的Local DNS到达指定的服务器,因此也就避免了DNS劫持问题。当然,域名和IP的对应通常通过服务器下发保证获取最近的资源节点(当然也可以采用一些收费的HTTPDNS服务),不过这样一来操作却不得不依赖于具体请求,而使用自定义NSURLProtocol的方式则可以彻底解决具体依赖问题,不管是使用NSURLConnection、NSURLSession还是UIWebView(WKWebView有所不同),所有的替换操作都可以统一进行控制。

下面的demo中自定义协议MyURLProtocol实现了将域名转化成IP进行请求的过程:

值得注意的是使用URLSession进行网络请求时如果使用的不是默认会话(URLSession.shared)需要在URLSessionConfiguration中指定protocolClasses,这样自定义URLProtocol才能进行处理。 在MyURLProtocol的startLoading方法内同样发起了URL请求,如果此时使用了URLSession.shared进行网络请求则同样会造成MyURLProtocol调用,如此会引起循环调用。考虑到startLoading方法能可能是NSURLConnnection实现,安全起见在MyURLProtocol内部使用URLProtocol.setProperty(true, forKey: MyCacheURLProtocolTagKey, in: newRequest)来标记一个请求,调用前使用URLProtocol.property(forKey: MyCacheURLProtocolTagKey, in: request)判断当前请求是否已经标记,如果已经标记则视为同一请求不再处理,从而避免同一个请求循环调用。

NSURLProtocol缓存

无论是NSURLConnection、NSURLSession还是UIWebView、WKWebView默认都是有缓存设计的(使用NSURLCache),不过这要配合服务器端response header使用,对于有缓存的页面(或者API接口),当缓存过期后,默认情况下(NSURLRequestUseProtocolCachePolicy)遇到同一个请求通常会发出一个header中包含If-Modified-Since的请求到服务器端验证,如果内容没有过期则返回一个不含有body的响应(Response code为304),客户端使用缓存数据,否则重新返回新的数据。

由于WKWebView默认有一段时间的缓存,在第一次缓存响应后过一段时间才会进行缓存请求检查(缓存过期后才会发送包含If-Modified-Since的请求检查)。不过它做不到完全的离线阅读(尽管在一定时间内不需要检查),而且无法做到缓存细节的控制。

下面简单利用NSURLProtocol来实现WKWebView的离线缓存功能,不过注意WKWebView默认仅仅调用NSURLProtocol的canInitWithRequest:方法,如果要真正利用NSURLProtocol进行缓存还必须使用WKBrowsingContextController的registerSchemeForCustomProtocol进行注册,但它是私有对象,需要动态设置。下面的demo中简单实现了WKWebView的离线缓存功能,这样有遇到访问过的资源即使没有网络也同样可以访问(当然,示例主要用以说明缓存的原理,实际开发中还有很多问题需要思考,比如说缓存过期机制、磁盘缓存保存方式等)。

NSURLCache

事实上,无论是NSURLConnection、URLSession还是UIWebView、WKWebView默认都是会使用缓存的(注意WKWebView的缓存配置是从iOS 9.0开始提供的,但是iOS 8.0中也同样包含缓存设计,不过没有提供缓存配置接口)。而NSURLConnection、NSURLSession和UIWebView默认都会使用NSURLCache,所有经过他们请求的数据都将被NSURLCache处理。NSURLCache不仅提供了内存和磁盘缓存方式,还有完善的缓存策略可配置。比如使用NRURLSession进行网络请求,就可以通过URLSessionConfiguration指定独立的URLCache(如果设置为nil则不再使用缓存缓存策略),通过URLSessionConfiguration的requestCachePolicy属性指定具体的缓存策略。

缓存策略CachePolicy

  • useProtocolCachePolicy:默认缓存策略,对于特定URL使用网络协议中实现的缓存策略。

  • reloadIgnoringLocalCacheData(或者reloadIgnoringCacheData):不使用缓存,直接请求原始数据。

  • returnCacheDataElseLoad:无论缓存是否过期,有缓存则使用缓存,否则重新请求原始数据。

  • returnCacheDataDontLoad:无论缓存是否过期,有缓存则使用缓存,否则视为失败,不会重新请求原始数据。

其实对于多数开发者而言默认缓存策略才是我们最关心的,这就有必要弄清HTTP的请求和响应是如何使用headers来进行元数据交换的(无论是NSURLConnection还是NSURLSession都支持多种协议,这里重点关注HTTP、HTTPS)。

请求头信息 Request cache headers

  • If-Modified-Since:与响应头Last-Modified相对应,其值为最后一次响应头中的Last-Modified。

  • If-None-Match:与响应头Etag相对应,其值为最后一次响应头中的Etag

响应头信息 Response cache headers

  • Last-Modified:资源最近修改时间

  • Etag:(Entity tag缩写)是请求资源的标识符,主要用于动态生成、没有Last-Modified值的资源。

  • Cache-Control:缓存控制,只有包含此设置可能使用默认缓存策略。可能包含如下选项: max-age:缓存时间(单位:秒)。 public:可以被任何区缓存,包括中间经过的代理服务器也可以缓存。通常不会被使用,因为 max-age本身已经表示此响应可以缓存。 private:只能被当前客户端缓存,中间代理无法进行缓存。 no-cache:必须与服务器端确认响应是否发生了变化,如果没有变化则可以使用缓存,否则使用新请求的响应。no-store:禁止使用缓存

  • Vary:决定请求是否可以使用缓存,通常作为缓存key值是否唯的确定因素,同一个资源不同的Vary设置会被作为两个缓存资源(注意:NSURLCache会忽略Vary请求缓存)。

注意:Expires是HTTP 1.0标准缓存控制,不建议使用,请使用_Cache-Control:max-age_代替,类似的还有Pragma:no-cache和Cache-Control:no-cache。此外,Request cache headers中也是可以包含Cache-Control的,例如如果设置为no-cache则说明此次请求不要使用缓存数据作为响应。

默认缓存策略下当客户端发起一个请求时首先会检查本地是否包含缓存,如果有缓存则检查缓存是否过期(通过_Cache-Control:max-age_或者_Expires_判断),如果没有过期则直接使用缓存数据。如果缓存过期了,则发起一个请求给服务器端,此时服务器端对比资源_Last-Modified_或者_Etags_(二者都存在的情况下下如果有一个不同则认为缓存已过期),如果不同则返回新数据,否则返回_304 Not Modified_继续使用缓存数据(客户端可以继续使用"max-age"秒缓存数据)。这个过程中客户端发送不发送请求主要看_max-age_是否过期,而过期后是否继续使用缓存则需要重新发起请求,服务器端根据情况通知客户端是否可以继续使用缓存(返回结果可能是200或者304)。

清楚了默认网络协议缓存相关的设置之后要使用默认缓存就比较简单了,通常对于NSURLSession你不做任何设置,只要服务器端响应头部加上Cache-Control:max-age:xxx就可以使用缓存了。下面Demo中演示了如何使用NSURLSession通过max-age进行为期60s的缓存,运行会发现在第一次请求之后60s内不会进行再次请求,60s后才会发起第二次请求。

服务器端default-cache.php内容如下:

对应的请求和响应头信息如下(服务器端设置缓存60s):

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

【Android 详细知识点思维脑图(技能树)】

我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

最后,赠与大家一句话,共勉!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

712389060007)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

最后,赠与大家一句话,共勉!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值