Prechádzať zdrojové kódy

Added CachedResponseHandler to allow per session and request response caching

Christian Noon 7 rokov pred
rodič
commit
998191d475

+ 10 - 0
Alamofire.xcodeproj/project.pbxproj

@@ -168,6 +168,10 @@
 		4C4466E721F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
 		4C4466E721F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
 		4C4466E821F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
 		4C4466E821F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
 		4C4466E921F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
 		4C4466E921F2866000AC9703 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466E521F2866000AC9703 /* RedirectHandler.swift */; };
+		4C4466EB21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */; };
+		4C4466EC21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */; };
+		4C4466ED21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */; };
+		4C4466EE21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */; };
 		4C743CF61C22772D00BCB23E /* certDER.cer in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F831C1A72F8002DA1A9 /* certDER.cer */; };
 		4C743CF61C22772D00BCB23E /* certDER.cer in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F831C1A72F8002DA1A9 /* certDER.cer */; };
 		4C743CF71C22772D00BCB23E /* certDER.crt in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F841C1A72F8002DA1A9 /* certDER.crt */; };
 		4C743CF71C22772D00BCB23E /* certDER.crt in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F841C1A72F8002DA1A9 /* certDER.crt */; };
 		4C743CF81C22772D00BCB23E /* certDER.der in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F851C1A72F8002DA1A9 /* certDER.der */; };
 		4C743CF81C22772D00BCB23E /* certDER.der in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F851C1A72F8002DA1A9 /* certDER.der */; };
@@ -386,6 +390,7 @@
 		4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManagerTests.swift; sourceTree = "<group>"; };
 		4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManagerTests.swift; sourceTree = "<group>"; };
 		4C43669A1D7BB93D00C38AAD /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Alamofire.swift"; sourceTree = "<group>"; };
 		4C43669A1D7BB93D00C38AAD /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Alamofire.swift"; sourceTree = "<group>"; };
 		4C4466E521F2866000AC9703 /* RedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedirectHandler.swift; sourceTree = "<group>"; };
 		4C4466E521F2866000AC9703 /* RedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedirectHandler.swift; sourceTree = "<group>"; };
+		4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedResponseHandler.swift; sourceTree = "<group>"; };
 		4C811F8C1B51856D00E0F59A /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustEvaluation.swift; sourceTree = "<group>"; };
 		4C811F8C1B51856D00E0F59A /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustEvaluation.swift; sourceTree = "<group>"; };
 		4C812C3A1B535F220017E0BF /* alamofire-root-ca.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = "alamofire-root-ca.cer"; path = "alamofire.org/alamofire-root-ca.cer"; sourceTree = "<group>"; };
 		4C812C3A1B535F220017E0BF /* alamofire-root-ca.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = "alamofire-root-ca.cer"; path = "alamofire.org/alamofire-root-ca.cer"; sourceTree = "<group>"; };
 		4C812C3D1B535F2E0017E0BF /* alamofire-signing-ca1.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = "alamofire-signing-ca1.cer"; path = "alamofire.org/alamofire-signing-ca1.cer"; sourceTree = "<group>"; };
 		4C812C3D1B535F2E0017E0BF /* alamofire-signing-ca1.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = "alamofire-signing-ca1.cer"; path = "alamofire.org/alamofire-signing-ca1.cer"; sourceTree = "<group>"; };
@@ -703,6 +708,7 @@
 		4CDE2C491AF8A14E00BABAE5 /* Features */ = {
 		4CDE2C491AF8A14E00BABAE5 /* Features */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				4C4466EA21F8F5D800AC9703 /* CachedResponseHandler.swift */,
 				3111CE8720A77843008315E2 /* EventMonitor.swift */,
 				3111CE8720A77843008315E2 /* EventMonitor.swift */,
 				4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */,
 				4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */,
 				311B198F20B0D3B40036823B /* MultipartUpload.swift */,
 				311B198F20B0D3B40036823B /* MultipartUpload.swift */,
@@ -1306,6 +1312,7 @@
 				4C3D00561C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4C3D00561C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				311B199220B0E3480036823B /* MultipartUpload.swift in Sources */,
 				311B199220B0E3480036823B /* MultipartUpload.swift in Sources */,
 				4C4466E821F2866000AC9703 /* RedirectHandler.swift in Sources */,
 				4C4466E821F2866000AC9703 /* RedirectHandler.swift in Sources */,
+				4C4466ED21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */,
 				319917A2209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				319917A2209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				4CF6270A1BA7CBF60011A099 /* ParameterEncoding.swift in Sources */,
 				4CF6270A1BA7CBF60011A099 /* ParameterEncoding.swift in Sources */,
 				31991796209CDA7F00103A19 /* Request.swift in Sources */,
 				31991796209CDA7F00103A19 /* Request.swift in Sources */,
@@ -1373,6 +1380,7 @@
 				4C811F8E1B51856D00E0F59A /* ServerTrustEvaluation.swift in Sources */,
 				4C811F8E1B51856D00E0F59A /* ServerTrustEvaluation.swift in Sources */,
 				311B199120B0E3470036823B /* MultipartUpload.swift in Sources */,
 				311B199120B0E3470036823B /* MultipartUpload.swift in Sources */,
 				4C4466E721F2866000AC9703 /* RedirectHandler.swift in Sources */,
 				4C4466E721F2866000AC9703 /* RedirectHandler.swift in Sources */,
+				4C4466EC21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */,
 				319917A1209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				319917A1209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				4C3D00551C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4C3D00551C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				31991795209CDA7F00103A19 /* Request.swift in Sources */,
 				31991795209CDA7F00103A19 /* Request.swift in Sources */,
@@ -1408,6 +1416,7 @@
 				E4202FD41B667AA100C997FB /* Alamofire.swift in Sources */,
 				E4202FD41B667AA100C997FB /* Alamofire.swift in Sources */,
 				311B199320B0E3480036823B /* MultipartUpload.swift in Sources */,
 				311B199320B0E3480036823B /* MultipartUpload.swift in Sources */,
 				4C4466E921F2866000AC9703 /* RedirectHandler.swift in Sources */,
 				4C4466E921F2866000AC9703 /* RedirectHandler.swift in Sources */,
+				4C4466EE21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */,
 				319917A3209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				319917A3209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				E4202FD51B667AA100C997FB /* MultipartFormData.swift in Sources */,
 				E4202FD51B667AA100C997FB /* MultipartFormData.swift in Sources */,
 				31991797209CDA7F00103A19 /* Request.swift in Sources */,
 				31991797209CDA7F00103A19 /* Request.swift in Sources */,
@@ -1443,6 +1452,7 @@
 				4C3D00541C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4C3D00541C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				311B199020B0D3B40036823B /* MultipartUpload.swift in Sources */,
 				311B199020B0D3B40036823B /* MultipartUpload.swift in Sources */,
 				4C4466E621F2866000AC9703 /* RedirectHandler.swift in Sources */,
 				4C4466E621F2866000AC9703 /* RedirectHandler.swift in Sources */,
+				4C4466EB21F8F5D800AC9703 /* CachedResponseHandler.swift in Sources */,
 				319917A0209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				319917A0209CDA7F00103A19 /* SessionStateProvider.swift in Sources */,
 				4CDE2C431AF89F0900BABAE5 /* Validation.swift in Sources */,
 				4CDE2C431AF89F0900BABAE5 /* Validation.swift in Sources */,
 				31991794209CDA7F00103A19 /* Request.swift in Sources */,
 				31991794209CDA7F00103A19 /* Request.swift in Sources */,

+ 92 - 0
Source/CachedResponseHandler.swift

@@ -0,0 +1,92 @@
+//
+//  CachedResponseHandler.swift
+//
+//  Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+
+import Foundation
+
+/// A type that handles whether the data task should store the HTTP response in the cache.
+public protocol CachedResponseHandler {
+    /// Determines whether the HTTP response should be stored in the cache.
+    ///
+    /// The `completion` closure should be passed one of three possible options:
+    ///
+    ///   1. The cached response provided by the server (this is the most common use case).
+    ///   2. A modified version of the cached response (you may want to modify it in some way before caching).
+    ///   3. A `nil` value to prevent the cached response from being stored in the cache.
+    ///
+    /// - Parameters:
+    ///   - task: The data task whose request resulted in the cached response.
+    ///   - response: The cached response to potentially store in the cache.
+    ///   - completion: The closure to execute containing cached response, a modified response, or `nil`.
+    func dataTask(_ task: URLSessionDataTask,
+                  willCacheResponse response: CachedURLResponse,
+                  completion: @escaping (CachedURLResponse?) -> Void)
+}
+
+// MARK: -
+
+/// `ResponseCacher` is a convenience `CachedResponseHandler` making it easy to cache, not cache, or modify a cached
+/// response.
+public struct ResponseCacher {
+    /// Defines the behavior of the `ResponseCacher` type.
+    ///
+    /// - cache:      Stores the cached response in the cache.
+    /// - doNotCache: Prevents the cached response from being stored in the cache.
+    /// - modify:     Modifies the cached response before storing it in the cache.
+    public enum Behavior {
+        case cache
+        case doNotCache
+        case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)
+    }
+
+    /// Returns a `ResponseCacher` with a follow `Behavior`.
+    public static let cache = ResponseCacher(behavior: .cache)
+    /// Returns a `ResponseCacher` with a do not follow `Behavior`.
+    public static let doNotCache = ResponseCacher(behavior: .doNotCache)
+
+    /// The `Behavior` of the `ResponseCacher`.
+    public let behavior: Behavior
+
+    /// Creates a `ResponseCacher` instance from the `Behavior`.
+    ///
+    /// - Parameter behavior: The `Behavior`.
+    public init(behavior: Behavior) {
+        self.behavior = behavior
+    }
+}
+
+extension ResponseCacher: CachedResponseHandler {
+    public func dataTask(_ task: URLSessionDataTask,
+                         willCacheResponse response: CachedURLResponse,
+                         completion: @escaping (CachedURLResponse?) -> Void) {
+        switch behavior {
+        case .cache:
+            completion(response)
+        case .doNotCache:
+            completion(nil)
+        case .modify(let closure):
+            let response = closure(task, response)
+            completion(response)
+        }
+    }
+}

+ 17 - 0
Source/Request.swift

@@ -82,6 +82,8 @@ open class Request {
         var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
         var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
         /// `RetryHandler` provided for redirect responses.
         /// `RetryHandler` provided for redirect responses.
         var redirectHandler: RedirectHandler?
         var redirectHandler: RedirectHandler?
+        /// `CachedResponseHandler` provided to handle caching responses.
+        var cachedResponseHandler: CachedResponseHandler?
         /// `URLCredential` used for authentication challenges.
         /// `URLCredential` used for authentication challenges.
         var credential: URLCredential?
         var credential: URLCredential?
         /// All `URLRequest`s created by Alamofire on behalf of the `Request`.
         /// All `URLRequest`s created by Alamofire on behalf of the `Request`.
@@ -141,6 +143,13 @@ open class Request {
         set { protectedMutableState.write { $0.redirectHandler = newValue } }
         set { protectedMutableState.write { $0.redirectHandler = newValue } }
     }
     }
 
 
+    // Cached Responses
+
+    public private(set) var cachedResponseHandler: CachedResponseHandler? {
+        get { return protectedMutableState.directValue.cachedResponseHandler }
+        set { protectedMutableState.write { $0.cachedResponseHandler = newValue } }
+    }
+
     // Credential
     // Credential
 
 
     /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods.
     /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods.
@@ -503,6 +512,14 @@ open class Request {
 
 
         return self
         return self
     }
     }
+
+    // MARK: - Cached Responses
+
+    @discardableResult
+    open func cacheResponse(with handler: CachedResponseHandler) -> Self {
+        protectedMutableState.write { $0.cachedResponseHandler = handler }
+        return self
+    }
 }
 }
 
 
 // MARK: - Protocol Conformances
 // MARK: - Protocol Conformances

+ 5 - 0
Source/Session.swift

@@ -35,6 +35,7 @@ open class Session {
     public let retrier: RequestRetrier?
     public let retrier: RequestRetrier?
     public let serverTrustManager: ServerTrustManager?
     public let serverTrustManager: ServerTrustManager?
     public let redirectHandler: RedirectHandler?
     public let redirectHandler: RedirectHandler?
+    public let cachedResponseHandler: CachedResponseHandler?
 
 
     public let session: URLSession
     public let session: URLSession
     public let eventMonitor: CompositeEventMonitor
     public let eventMonitor: CompositeEventMonitor
@@ -53,6 +54,7 @@ open class Session {
                 retrier: RequestRetrier? = nil,
                 retrier: RequestRetrier? = nil,
                 serverTrustManager: ServerTrustManager? = nil,
                 serverTrustManager: ServerTrustManager? = nil,
                 redirectHandler: RedirectHandler? = nil,
                 redirectHandler: RedirectHandler? = nil,
+                cachedResponseHandler: CachedResponseHandler? = nil,
                 eventMonitors: [EventMonitor] = []) {
                 eventMonitors: [EventMonitor] = []) {
         precondition(session.delegate === delegate,
         precondition(session.delegate === delegate,
                      "SessionManager(session:) initializer must be passed the delegate that has been assigned to the URLSession as the SessionDataProvider.")
                      "SessionManager(session:) initializer must be passed the delegate that has been assigned to the URLSession as the SessionDataProvider.")
@@ -69,6 +71,7 @@ open class Session {
         self.retrier = retrier
         self.retrier = retrier
         self.serverTrustManager = serverTrustManager
         self.serverTrustManager = serverTrustManager
         self.redirectHandler = redirectHandler
         self.redirectHandler = redirectHandler
+        self.cachedResponseHandler = cachedResponseHandler
         eventMonitor = CompositeEventMonitor(monitors: defaultEventMonitors + eventMonitors)
         eventMonitor = CompositeEventMonitor(monitors: defaultEventMonitors + eventMonitors)
         delegate.eventMonitor = eventMonitor
         delegate.eventMonitor = eventMonitor
         delegate.stateProvider = self
         delegate.stateProvider = self
@@ -84,6 +87,7 @@ open class Session {
                             retrier: RequestRetrier? = nil,
                             retrier: RequestRetrier? = nil,
                             serverTrustManager: ServerTrustManager? = nil,
                             serverTrustManager: ServerTrustManager? = nil,
                             redirectHandler: RedirectHandler? = nil,
                             redirectHandler: RedirectHandler? = nil,
+                            cachedResponseHandler: CachedResponseHandler? = nil,
                             eventMonitors: [EventMonitor] = []) {
                             eventMonitors: [EventMonitor] = []) {
         let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.sessionManager.sessionDelegateQueue")
         let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.sessionManager.sessionDelegateQueue")
         let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
         let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
@@ -97,6 +101,7 @@ open class Session {
                   retrier: retrier,
                   retrier: retrier,
                   serverTrustManager: serverTrustManager,
                   serverTrustManager: serverTrustManager,
                   redirectHandler: redirectHandler,
                   redirectHandler: redirectHandler,
+                  cachedResponseHandler: cachedResponseHandler,
                   eventMonitors: eventMonitors)
                   eventMonitors: eventMonitors)
     }
     }
 
 

+ 6 - 1
Source/SessionStateProvider.swift

@@ -29,6 +29,7 @@ public protocol SessionStateProvider: AnyObject {
     func didCompleteTask(_ task: URLSessionTask)
     func didCompleteTask(_ task: URLSessionTask)
     var serverTrustManager: ServerTrustManager? { get }
     var serverTrustManager: ServerTrustManager? { get }
     var redirectHandler: RedirectHandler? { get }
     var redirectHandler: RedirectHandler? { get }
+    var cachedResponseHandler: CachedResponseHandler? { get }
     func credential(for task: URLSessionTask, protectionSpace: URLProtectionSpace) -> URLCredential?
     func credential(for task: URLSessionTask, protectionSpace: URLProtectionSpace) -> URLCredential?
 }
 }
 
 
@@ -190,7 +191,11 @@ extension SessionDelegate: URLSessionDataDelegate {
                          completionHandler: @escaping (CachedURLResponse?) -> Void) {
                          completionHandler: @escaping (CachedURLResponse?) -> Void) {
         eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
         eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse)
 
 
-        completionHandler(proposedResponse)
+        if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler {
+            handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
+        } else {
+            completionHandler(proposedResponse)
+        }
     }
     }
 }
 }