IETF 在 RFC 2616 中明确定义了 Web 浏览器与 Web 服务器之间的 HTTP 缓存的工作方式, 可以在 http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html 上找到相关信息. HTTP 被设计为针对浏览器与服务器之间的通信, 缓存机制也是针对这种使用模式的. iOS 提供了一种机制来利用标准 HTTP 缓存, 以及采取相应的行为. 通过 NSURLRequest 发出的每个请求都会经过缓存组件. 该组件是 NSURLCache 或其子类的实例. 这个对象是 iOS 采用的管理来自服务器的响应缓存的标准机制
1. 默认缓存行为
在默认情况下, NSURLRequest 遵照 RFC 2616 来管理缓存. 该默认行为指定缓存要返回大多数当前的内容副本. 如果返回的内容不是最新的, 就会发出警告; 如果无法返回内容, 则报告错误
在 iOS 上, 这意味着只有在响应头表明响应能够缓存的情况下, 当第一次返回时才会将其缓存到内存中. 对于后续发送给相同 URL 的请求来说, iOS 会使用 If-Modified-Since 头(包含缓存响应的修改日期与时间)向服务器发送请求. 如果服务器确定自从请求头提供的时间开始内容没有被修改, 就会返回状态为 304 的响应, 并且没有响应体. 通过这个响应, iOS 能够确定它所缓存的副本是最新的内容, 并使用 200 状态码返回, 这很有效地对应用代码隐藏了缓存活动. 在内容来自于内容分发网络(CDN)的网络配置中, 源 URL 对于不同的请求来说可能是不同的, 因此对于 HTTP 标准定义的缓存机制是不适用的
这些标准的缓存规则的设计专门针对与 Web 浏览器的交互. 使用 HTTP 作为传输协议的移动应用可以适当修改这些规则来改进性能并满足应用的需求. iOS 中的 URL 加载系统向客户端应用提供了一种方式来覆写默认行为. 在覆写默认行为时, 你需要花一些时间来充分理解可能会导致应用出现缺陷的边际行为
可以通过为请求设定缓存策略来覆写默认的缓存规则
iOS 提供了 6 种不同的设置, 使得开发者能够控制响应缓存的方式:
- NSURLRequestUseProtocolCachePolicy - 该设置告诉系统遵照 RFC 2616 指定的规则
- NSURLRequestReloadIgnoringLocalCacheData - 该设置告诉请求略过本地缓存, 从网络上检索新的内容. 如果某些网络设备(如缓存网络代理)介于应用与数据源之间, 并且持有内容的缓存副本, 就会返回缓存副本
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData - 该设置告诉请求略过本地缓存并将头添加到请求中, 同时告诉中间设备也略过缓存, 提供源服务器上的最新数据
- NSURLRequestReturnCacheDataElseLoad - 该设置会让缓存系统返回一份内容的缓存副本而不去验证服务器上是否有最新的副本. 如果请求的缓存副本在缓存中存在, 就会将其返回. 如果缓存副本不存在, 那就通过网络请求检索内容. 该设置提供了最快的响应时间, 但却最有可能返回过期的数据. 要想通过该设置带来收益, 请使用该类型的请求向用户提供最初的快速响应, 然后在后台线程中发出请求, 使用服务器的最新数据来刷新缓存
- NSURLRequestReturnCacheDataDontLoad - 该设置指定只返回缓存中的内容. 如果内容不在缓存中, 那就会返回错误而不是从服务器上获取内容
- NSURLRequestReloadRevalidatingCacheData - 该设置总是会重新验证数据. 在某些情况下, 缓存的响应可能会有过期时间, 到了这个时间后系统就会检查最新的数据. 如果使用该设置, 那就会忽略掉过期时间, 并且总是验证服务器有没有最新的内容
除了配置每个请求使用缓存的方式外, 还可以通过配置应用的 NSURLCache 对象来指定应用所能缓存的数据量
2. 配置 NSURLCache
在应用使用任何标准的 iOS 类创建网络请求时, 系统都会创建 NSURLCache 实例. 在默认情况下, 该实例只会将数据缓存在 RAM 中, 这意味着当程序退出时, 其缓存的请求就会被清空. 当设备处于低内存状态时也会清空 RAM 缓存
iOS 提供了一种方式来重新定义默认的缓存, 并指定了更大的内存容量与持久化存储, 以便缓存在应用重启后依然可以使用.
该例创建 1MB 的内存缓存和 20MB 的持久化缓存. 缓存数据库的位置位于应用的沙箱, 在 Library/Caches 目录下, 文件名为 URLCache. 示例代码的第 2 行将应用的缓存实例设定为上一行创建的那一个
iOS 中有一种奇怪的现象, 即在某些情况下, 应用中的系统组件会将缓存的内存容量设为 0MB, 这就禁用了缓存. 解决了这个无法解释的行为的一种方式就是通过自己的实现子类化 NSURLCache, 拒绝将内存缓存大小设为 0.
setMemoryCapacity: 方法中的代码会验证大小不为 0, 并调用父类, 从而将新的大小设为除了 0 之外的其他值
可以通过压缩数据及管理化请求以最大化地提升应用的性能, 不过最快的请求实际上是没有发出的请求. 通过仔细考虑应用需求以及服务器的行为, 可以将数据保留在缓存中, 只有当服务器上的数据发生变化时才刷新, 从而避免发出这些请求
7.3 小结
iOS 用户都希望应用能够立刻响应每个请求. 移动产业有这样一条原则, 即屏幕越小, 用户越没耐心. 提供让用户乐于使用的应用意味着要珍惜用户的时间, 就像珍惜你自己的时间一样. 通过压缩请求与响应来优化应用所使用的带宽, 通过管道化请求避免不必要的延迟, 甚至通过缓存响应来避免冗余的网络请求都会加速应用并改进用户体验