本文转载自崔江涛(KenshinCui)

缓存设计

从前面对于URL Loading System的分析可以看出利用NSURLProtocol或者NSURLCache都可以做客户端缓存,但是NSURLProtocol更多的用于拦截处理,而且如果使用它来做缓存的话需要自己发起请求。而选择URLSession配合NSURLCache的话,则对于接口调用方有更多灵活的控制,而且默认情况下NSURLCache就有缓存,我们只要操作缓存响应的Cache headers即可,因此后者作为我们优先考虑的设计方案。鉴于本文代码使用Swift编写,因此结合目前Swift中流行的网络库Alamofire实现一种相对简单的缓存方案。

根据前面的思路,最早还是想从URLSessionDataDelegate的缓存设置方法入手,而且Alamofire确实对于每个URLSessionDataTask都留有缓存代理方法的回调入口,但查看源码发现这个入口dataTaskWillCacheResponse并未对外开发,而如果直接在SessionDelegate的回调入口dataTaskWillCacheResponseWithCompletion上进行回调又无法控制每个请求的缓存情况(NSURLSession是多个请求共用的)。当然如果沿着这个思路可以再扩展一个DataTaskDelegate对象以暴漏缓存入口,但是这么一来必须实现URLSessionDataDelegate,而且要想办法Swizzle NSURLSession的缓存代理(或者继承SessionDelegate切换代理),在代理中根据不同的NSURLDataTask进行缓存处理,整个过程对于调用方并不是太友好。

另一个思路就是等Response请求结束后获取缓存的响应CachedURLResponse并且修改(事实上只要是同一个NSURLRequest存储进去默认会更新原有缓存),而且NSURLCache本身就是有内存缓存的,过程并不会太耗时。当然这个方案最重要的是得保证响应完成,所以这里通过Alamofire链式调用使用response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler重新请求以保证及时掌握回调时机。主要的代码片段如下:

public func cache(maxAge:Int,isPrivate:Bool = false,ignoreServer:Bool = true)

-> Self

{

var useServerButRefresh = false

if let newRequest = self.request {

if !ignoreServer {

if newRequest.allHTTPHeaderFields?[AlamofireURLCache.refreshCacheKey] == AlamofireURLCache.RefreshCacheValue.refreshCache.rawValue {

useServerButRefresh = true

}

}

if newRequest.allHTTPHeaderFields?[AlamofireURLCache.refreshCacheKey] != AlamofireURLCache.RefreshCacheValue.refreshCache.rawValue {

if let urlCache = self.session.configuration.urlCache {

if let value = (urlCache.cachedResponse(for: newRequest)?.response as? HTTPURLResponse)?.allHeaderFields[AlamofireURLCache.refreshCacheKey] as? String {

if value == AlamofireURLCache.RefreshCacheValue.useCache.rawValue {

return self

}

}

}

}

}

return response { [unowned self](defaultResponse) in

if defaultResponse.request?.httpMethod != "GET" {

debugPrint("Non-GET requests do not support caching!")

return

}

if defaultResponse.error != nil {

debugPrint(defaultResponse.error!.localizedDescription)

return

}

if let httpResponse = defaultResponse.response {

guard let newRequest = defaultResponse.request else { return }

guard let newData = defaultResponse.data else { return }

guard let newURL = httpResponse.url else { return }

guard let urlCache = self.session.configuration.urlCache else { return }

guard let newHeaders = (httpResponse.allHeaderFields as NSDictionary).mutableCopy() as? NSMutableDictionary else { return }

if AlamofireURLCache.isCanUseCacheControl {

if httpResponse.allHeaderFields["Cache-Control"] == nil || httpResponse.allHeaderFields.keys.contains("no-cache") || httpResponse.allHeaderFields.keys.contains("no-store") || ignoreServer || useServerButRefresh {

DataRequest.addCacheControlHeaderField(headers: newHeaders, maxAge: maxAge, isPrivate: isPrivate)

} else {

return

}

} else {

if httpResponse.allHeaderFields["Expires"] == nil || ignoreServer || useServerButRefresh {

DataRequest.addExpiresHeaderField(headers: newHeaders, maxAge: maxAge)

if ignoreServer && httpResponse.allHeaderFields["Pragma"] != nil {

newHeaders["Pragma"] = "cache"

}

} else {

return

}

}

newHeaders[AlamofireURLCache.refreshCacheKey] = AlamofireURLCache.RefreshCacheValue.useCache.rawValue

if let newResponse = HTTPURLResponse(url: newURL, statusCode: httpResponse.statusCode, httpVersion: AlamofireURLCache.HTTPVersion, headerFields: newHeaders as? [String : String]) {

let newCacheResponse = CachedURLResponse(response: newResponse, data: newData, userInfo: ["framework":AlamofireURLCache.frameworkName], storagePolicy: URLCache.StoragePolicy.allowed)

urlCache.storeCachedResponse(newCacheResponse, for: newRequest)

}

}

}

}

要完成整个缓存处理自然还包括缓存刷新、缓存清理等操作,关于缓存清理本身NSURLCache是提供了remove方法的,不过缓存清理并不及时,调用并不会立即生效,具体参见NSURLCache does not clear stored responses in iOS8。因此,这里借助了上面提到的Cache-Control进行缓存过期控制,一方面可以快速清理缓存,另一方面缓存控制可以更加精确。

AlamofireURLCache

为了更好的配合Alamofire使用,此代码以AlamofireURLCache类库形式在github开源,所有接口API尽量和原有接口保持一致,便于对Alamofire二次封装。此外还提供了手动清理缓存、出错之后自动清理缓存、覆盖服务器端缓存配置等方便的功能,可以满足多数情况下缓存需求细节。

AlamofireURLCache在request方法添加了refreshCache参数用于缓存刷新,设为false或者不提供此参数则不会刷新缓存,只有等到上次缓存数据过了有效期才会再次发起请求。

Alamofire.request("https://myapi.applinzi.com/url-cache/no-cache.php",refreshCache:false).responseJSON(completionHandler: { response in

if response.value != nil {

self.textView.text = (response.value as! [String:Any]).debugDescription

} else {

self.textView.text = "Error!"

}

}).cache(maxAge: 10)

服务器端缓存headers设置并不都是最优选择,某些情况下客户端必须自行控制缓存策略,此时可以使用AlamofireURLCache的ignoreServer参数忽略服务器端配置,通过maxAge参数自行控制缓存时长。

Alamofire.request("https://myapi.applinzi.com/url-cache/default-cache.php",refreshCache:false).responseJSON(completionHandler: { response in

if response.value != nil {

self.textView.text = (response.value as! [String:Any]).debugDescription

} else {

self.textView.text = "Error!"

}

}).cache(maxAge: 10,isPrivate: false,ignoreServer: true)

另外,有些情况下未必需要刷新缓存而是要清空缓存保证下次访问时再使用最新数据,此时就需要使用AlamofireURLCache提供的缓存清理API来完成。需要特别说明的是,对于请求出错、序列化出错等情况如果调用了cache(maxAge)方法进行缓存后,那么下次请求会使用错误的缓存数据,需要开发人员根据返回情况自行调用API清理缓存。但更好的选择是使用AlamofireURLCache提供的autoClearCache参数来自动处理此种情况,所以任何时候都推荐将autoClearCache参数设为true以保证不会缓存出错数据。

Alamofire.clearCache(dataRequest: dataRequest) // clear cache by DataRequest

Alamofire.clearCache(request: urlRequest) // clear cache by URLRequest

// ignore data cache when request error

Alamofire.request("https://myapi.applinzi.com/url-cache/no-cache.php",refreshCache:false).responseJSON(completionHandler: { response in

if response.value != nil {

self.textView.text = (response.value as! [String:Any]).debugDescription

} else {

self.textView.text = "Error!"

}

},autoClearCache:true).cache(maxAge: 10)

如果阅读本文让你有所收获,欢迎推荐点赞,最后再次附上代码下载!

代码下载

iOS架构设计-URL缓存(下)相关推荐

  1. iOS架构设计-URL缓存(上)

    转载自崔江涛(KenshinCui) http://www.cnblogs.com/kenshincui/p/iOS-jia-gou-she-jiURL-huan-cun.html 概览 缓存组件应该 ...

  2. 架构设计 | 分布式体系下,服务分层监控策略

    本文源码:GitHub·点这里 || GitEE·点这里 一.分布式故障 分布式系统的架构,业务开发,这些在良好的思路和设计文档规范之下,是相对来说好处理的,这里的相对是指比较分布式架构下生产环境的突 ...

  3. iOS架构设计(一)- MVC

    谈起架构,是个很大很大的词,在开发行业里似乎又是个很虚很虚的词,一般情况下,我都是很少去阐述,更多的是应用到自己平时的工作跟解决问题中 人人都可以谈架构,毕竟谈起来又不需要备案,合适与否,无从可知 架 ...

  4. 视频教程-iOS架构设计与底层开发-iOS

    iOS架构设计与底层开发 毕业于解放军特种作战学院,曾就职广州军区司令部.复原后从事IT行业,涉及逆向,安全,密码学等技术领域.2014年在广州某线下教育机构担任iOS讲师,2015年加入潭州教育集团 ...

  5. iOS架构设计(三)- MVVM

    我是不敢轻易谈MVVM架构设计的 终于在经过前面几篇文章内容的铺垫之后,现在简单说说自己的想法 切记,如果没有kvc kvo的原理知识铺垫,最好去复习一下,否则看过了解,回头就会忘却,不会形成意识 K ...

  6. iOS架构设计与底层开发-李文瀚-专题视频课程

    iOS架构设计与底层开发-418人已学习 课程介绍         课程内容包括以下:架构设计与底层开发两大知识点 课程收益     通过碎片化学习,熟练掌握架构设计与底层开发,以及最新iOS逆向编程 ...

  7. iOS架构设计-关东升-专题视频课程

    iOS架构设计-3563人已学习 课程介绍         移动平台分层架构设计:大到企业级系统,小到移动设备,我们需要架构设计,因为设计是大道之理,那么分层是将一个系统分成相似技术的模块,这样做的目 ...

  8. iOS架构设计:最初的MVC

    这是对1979年5月12日Trygve Reenskaug提出MVC时的文章的翻译. 原始MVC报告 -- 来自奥斯陆大学信息学系Trygve Reenskaug 1978/79年,我在施乐帕洛阿尔托 ...

  9. 高性能网站架构设计之缓存篇(5)- Redis 集群(上)

    2019独角兽企业重金招聘Python工程师标准>>> 集群技术是构建高性能网站架构的重要手段,试想在网站承受高并发访问压力的同时,还需要从海量数据中查询出满足条件的数据,并快速响应 ...

最新文章

  1. H-Net:基于无监督注意的立体深度估计
  2. 【错误总结】LaTex Warning: citation undefined
  3. VTK:可视化之ColorSeriesPatches
  4. php qcloud sdk weapp_微信小程序源码+PHP后台
  5. Java订单交易_Java实现获取105发卡平台的订单信息
  6. SQLSTATE[HY000] [2013] Lost connection to MySQL...
  7. ext.net 开发学习——回车事件(六)
  8. 我为什么要使用Webpack?
  9. Oracle傻瓜手册
  10. ios状态栏字体颜色设置白色
  11. 超全!0基础程序员从入门到工作(持续更新...)
  12. 机器搜索引擎 vs 人肉搜索引擎(作者:胡宝介)
  13. 关于RS485的使用
  14. js声明变量过程,程序都做了什么?
  15. Python爬取EF每日英语资源
  16. 计算机技术职业资格考试
  17. 万能五笔输入法弹窗_万能五笔输入法的广告怎么关闭
  18. Map Coloring
  19. WPF 触摸屏小键盘样式
  20. 揭露数据不一致的利器 —— 实时核对系统

热门文章

  1. 记一次 HTTP信息头管理器使用 的重要性
  2. 全面认识一下.NET 4.0的缓存功能
  3. php内核探索方法与资源
  4. form表单的reset
  5. 使用Varnish+ESI实现静态页面的局部缓存
  6. 织梦内容管理系统修改
  7. 家庭局域网开启AP隔离利用无线路由器互连
  8. 手机内存RAM、ROM简介
  9. 【ubuntu】在ubuntu下无法输出拼音输入法中的中括号“【” 和 “】”的解决方法
  10. hadoop mysql mybatis_MyBatis简介与配置MyBatis+Spring+MySql