专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

iOS架构设计-URL缓存(下)

Cocoa开发者社区  · 公众号  · ios  · 2017-07-15 11:05

正文

本文转载自崔江涛(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 {







请到「今天看啥」查看全文