Alamofire源码学习(十三): Cache

1,469 阅读4分钟

往期导航:

Alamofire源码学习目录合集

ps: 时隔两个月,忙成狗,终于有时间来继续写点东西了……

简介:

上一篇是响应与解析,这篇是缓存,本体很简单,也是用了接口隔离,主要是对HTTPCache的预处理,HTTP的缓存是通过响应头中的相关字段来控制缓存相关参数,URLSession会自动管理缓存,Alamofire定义了一个CachedResponseHandler协议来对缓存进行了预处理,允许用户在URLSession即将缓存响应数据前,对响应进行修改处理操作。

URLSession中的缓存处理

URLSession的URLSessionDataDelegate中有声明处理缓存的方法:

/* Invoke the completion routine with a valid NSCachedURLResponse to
 * allow the resulting data to be cached, or pass nil to prevent
 * caching. Note that there is no guarantee that caching will be
 * attempted for a given resource, and you should not rely on this
 * message to receive the resource data.
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                  willCacheResponse:(NSCachedURLResponse *)proposedResponse 
                                  completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;

URLSession会根据响应头中的Cache相关字段自行处理缓存,使用接口提供给用户修改缓存的机会,改方法允许用户在缓存响应数据前,对响应数据进行修改,修改完成后必须要调用completionHandle,入参不为空则缓存参数缓存数据,入参为空则不缓存该响应。

Alamofire的抽象封装与遍历缓存管理器

Alamofire定义了CachedResponseHandler协议来处理缓存操作,该协议只有一个方法:

public protocol CachedResponseHandler {
    /// 决定缓存处理操作
    ///
    /// completion回调参数有三种情况:
    /// 1.入参的第二个参数(不改变数据,直接缓存,默认行为)
    /// 2.对response缓存数据修改后的新数据(适合需要对数据进行修改后再缓存)
    /// 3.nil,不缓存respose
    ///
    ///
    /// - Parameters:
    ///   - task:       请求task
    ///   - response:   即将要缓存的响应数据
    ///   - completion: 决定缓存行为的完成回调
    func dataTask(_ task: URLSessionDataTask,
                  willCacheResponse response: CachedURLResponse,
                  completion: @escaping (CachedURLResponse?) -> Void)
}

该协议有两个类在使用:

  1. SessionDelegate中声明了SessionStateProvider协议,该协议中提供了全局的cacheResponseHandler,Session类遵循了该协议,在初始化时设置全局的cacheResponseHandler。
  2. Request基类中定义了可选变量cacheResponseHandler,可以对单个请求使用专用的缓存处理器,优先级高于全局缓存处理器

然后在SessionDelegate实现的URLSessionDataDelegate的缓存处理方法中,使用潜在的缓存处理器来处理缓存

    //处理是否保存缓存
    open func urlSession(_ session: URLSession,
                         dataTask: URLSessionDataTask,
                         willCacheResponse proposedResponse: CachedURLResponse,
                         completionHandler: @escaping (CachedURLResponse?) -> Void) {
        //通知监听器
        eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)

        //先获取Request的缓存处理器,为空再去获取Session的全局缓存处理器
        if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
            //用缓存处理器处理缓存
            handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
        } else {
            //没有缓存处理器的话,采用默认逻辑处理缓存
            completionHandler(proposedResponse)
        }
    }

Alamofire中定义的简易缓存处理器ResponseCacher

Alamofire中定义了个ResponseCacher,这是个简易的缓存处理器,其中定义了一个枚举Behavior,可以决定保存缓存的逻辑:

public struct ResponseCacher {
    /// 定义处理缓存的行为
    public enum Behavior {
        /// 缓存原始数据
        case cache
        /// 不缓存
        case doNotCache
        /// 先修改数据,再缓存新的数据,参数为修改数据的闭包,改闭包返回可选的新缓存数据
        case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)
    }

    /// 快速初始化缓存原始数据的对象
    public static let cache = ResponseCacher(behavior: .cache)
    /// 快速初始化不缓存的对象
    public static let doNotCache = ResponseCacher(behavior: .doNotCache)

    /// 缓存行为
    public let behavior: Behavior

    /// 初始化
    public init(behavior: Behavior) {
        self.behavior = behavior
    }
}

接着扩展ResposeCacher,实现CachedResponseHandler协议,根据Behavior的类型来调用completion回调

extension ResponseCacher: CachedResponseHandler {
    public func dataTask(_ task: URLSessionDataTask,
                         willCacheResponse response: CachedURLResponse,
                         completion: @escaping (CachedURLResponse?) -> Void) {
        switch behavior {
        case .cache:
            //缓存原始数据,直接传原始数据给completion
            completion(response)
        case .doNotCache:
            //不缓存数据,传nil给completion
            completion(nil)
        case let .modify(closure):
            //修改,先调用参数closure获取修改数据,然后传给completion
            let response = closure(task, response)
            completion(response)
        }
    }
}

总结

Alamofire中对于缓存的处理只是做了简单的抽象封装,把原本需要代理处理的操作,包装成了是用协议对象来处理,我们用的时候,可以简单的直接使用ResponseCacher,也可以自己针对自己的业务逻辑实现自己的缓存处理器,十分灵活。
不过URLSession的缓存处理,依赖于响应头中的缓存参数,因此像缓存的有效期,认证等,更多需要依赖于后台处理,如果需要app自行缓存全部数据到本地数据库,那就需要使用EventModifier + RequestAdapter来自己手动保存响应,并在请求发出后手动读取缓存,一切取舍看实际业务需求~

以上纯属个人理解,难免有误,如发现有错误的地方,欢迎评论指出,将第一时间修改,非常感谢~