Alamofire: cache issue on iOS 8 ?

Created on 11 Apr 2016  路  5Comments  路  Source: Alamofire/Alamofire

Hi guys,

Is there an issue for caching on 8.x ?

this:

public func URLSession(
            session: NSURLSession,
            dataTask: NSURLSessionDataTask,
            willCacheResponse proposedResponse: NSCachedURLResponse,
            completionHandler: ((NSCachedURLResponse?) -> Void))
        {
            if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
                completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse))
            } else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
                delegate.URLSession(
                    session,
                    dataTask: dataTask,
                    willCacheResponse: proposedResponse,
                    completionHandler: completionHandler
                )
            } else {
                completionHandler(proposedResponse)
            }
        }

is never called in iOS 8.X but it's is in iOS 9.X...i have nothing in my code to differentiate iOS 8 and 9 (maybe that's the issue)

I'm currently on Alamofire 3.2.1 maybe the 3.3 fixed the issue but i didn't to go to swift 2.2 right now..

Thanks for future answers

question session delegate

Most helpful comment

Sure but do you have any idea how i can demonstrate the issue ?
I dont have any error in the logs, it's just that the cache worked on iOS 9 and not iOS 8

i can post you some of the code we are using:
Our service class:

import Foundation
import Alamofire

class MyCCHTTPService: NSObject {

    var manager : Manager?

    var cachePolicy : NSURLRequestCachePolicy = .UseProtocolCachePolicy

    var cacheDuration : NSTimeInterval = 24*60*60

    var timeOut : NSTimeInterval = 30

    var debugMode : Bool = false

    override init(){
        super.init()
        self.manager = Manager(configuration: urlSessionConfiguration())
        self.manager?.delegate.dataTaskWillCacheResponse = {
            (urlSession : NSURLSession, dataTask : NSURLSessionDataTask, cachedResponse : NSCachedURLResponse) -> NSCachedURLResponse in

            let response = cachedResponse.response as! NSHTTPURLResponse
            var headers = response.allHeaderFields as! [String : String]

            headers.removeValueForKey("Expires")
            headers.removeValueForKey("s-maxage")
            headers["Cache-Control"] = String(format: "max-age=%li",self.cacheDuration)

            let modifiedResponse = NSHTTPURLResponse(
                URL: response.URL!,
                statusCode: response.statusCode,
                HTTPVersion: "HTTP/1.1",
                headerFields: headers)

            let modifiedCachedResponse = NSCachedURLResponse(
                response: modifiedResponse!,
                data: cachedResponse.data,
                userInfo: cachedResponse.userInfo,
                storagePolicy: cachedResponse.storagePolicy)

            return modifiedCachedResponse
        }
    }

    private func urlSessionConfiguration() -> NSURLSessionConfiguration{
        let urlSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
        urlSessionConfiguration.requestCachePolicy = cachePolicy
        urlSessionConfiguration.timeoutIntervalForResource = timeOut
        urlSessionConfiguration.timeoutIntervalForRequest = timeOut
        return urlSessionConfiguration
    }

    func logOperation<T>(response : Response<T,NSError>){
        if(self.debugMode){
            print(response.debugDescription)
        }
    }
}

Our router class:

import Alamofire
import Foundation


enum CMSRouter: URLRequestConvertible {
    static let hostURL = NSURL(string: NSUserDefaults.standardUserDefaults().objectForKey(NSUserDefaults.SettingsKeys.CMS, forceLoadSettings: true) as! String)!

    static let baseURL = NSURL(string: "/base/url",relativeToURL: hostURL)


    var URL : NSURL { return CMSRouter.baseURL!.URLByAppendingPathComponent(route.path) }


    // Router Cases
    case ActiveStores
    case EmailDomain
    case Languages(String)
    case FieldControlDisplay(String)
    case Texts(String,String)
    case Titles(String,String)
    case Countries(String,String)
    case Nationalities(String,String)
    case AddressFieldsOrder(String,String)
    case ClientType

    // MARK: Route from cases

    var route : (path: String, parameters: [String: AnyObject]?) {
        switch self{
        case .ActiveStores:
            return ("/api1",nil)
        case .EmailDomain:
            return ("/api2",nil)
        case .Languages(let zoneCode):
            return ("/api3/\(zoneCode)",nil)
        case .FieldControlDisplay(let zoneCode):
            return ("/api4/\(zoneCode)",nil)
        case .Texts(let zoneCode, let lang):
            return ("/api5/\(zoneCode)/\(lang)",nil)
        case .Titles(let zoneCode, let lang):
            return ("/api6/\(zoneCode)/\(lang)",nil)
        case .Countries(let zoneCode, let lang):
            return ("/api7/\(zoneCode)/\(lang)",nil)
        case .Nationalities(let zoneCode, let lang):
            return ("/api8/\(zoneCode)/\(lang)",nil)
        case .AddressFieldsOrder(let zoneCode, let lang):
            return ("/api9/\(zoneCode)/\(lang)",nil)
        case .ClientType:
            return ("/api10/",nil)

        }

    }

    var URLRequest: NSMutableURLRequest {
        return
            Alamofire
                .ParameterEncoding
                .URL
                .encode(NSURLRequest(URL: URL), parameters: route.parameters).0
    }
}

And the service child which implement the functions we are calling:

import Foundation
import Alamofire

class MyCCCMSHTTPService: MyCCHTTPService, MyCCCMSService {

    // MARK: configuration/stores/active
    func fetchStoresActive(completionHandler : CountryStores -> Void, failureHandler : NSError -> Void) -> Void{
        self.manager!.request(CMSRouter.ActiveStores)
            .responseModelRocketJSON { (response : Response<CountryStores,NSError>) -> Void in
                self.logOperation(response)
                if(response.result.error != nil){
                    failureHandler(response.result.error!)
                }
                else{
                    completionHandler(response.result.value!)
                }
        };
    }

so the problem is that self.manager?.delegate.dataTaskWillCacheResponse is never used on iOS8...
I got nothing to specify to use cache, and receiving nothing in the response header. For iOS 9 it's works perfectly :/

All 5 comments

Hmmmm....we'll need to look into this @Mycose. Is there any chance you could put together a failing test to demonstrate the issue? That would make it much easier for us to troubleshoot.

Sure but do you have any idea how i can demonstrate the issue ?
I dont have any error in the logs, it's just that the cache worked on iOS 9 and not iOS 8

i can post you some of the code we are using:
Our service class:

import Foundation
import Alamofire

class MyCCHTTPService: NSObject {

    var manager : Manager?

    var cachePolicy : NSURLRequestCachePolicy = .UseProtocolCachePolicy

    var cacheDuration : NSTimeInterval = 24*60*60

    var timeOut : NSTimeInterval = 30

    var debugMode : Bool = false

    override init(){
        super.init()
        self.manager = Manager(configuration: urlSessionConfiguration())
        self.manager?.delegate.dataTaskWillCacheResponse = {
            (urlSession : NSURLSession, dataTask : NSURLSessionDataTask, cachedResponse : NSCachedURLResponse) -> NSCachedURLResponse in

            let response = cachedResponse.response as! NSHTTPURLResponse
            var headers = response.allHeaderFields as! [String : String]

            headers.removeValueForKey("Expires")
            headers.removeValueForKey("s-maxage")
            headers["Cache-Control"] = String(format: "max-age=%li",self.cacheDuration)

            let modifiedResponse = NSHTTPURLResponse(
                URL: response.URL!,
                statusCode: response.statusCode,
                HTTPVersion: "HTTP/1.1",
                headerFields: headers)

            let modifiedCachedResponse = NSCachedURLResponse(
                response: modifiedResponse!,
                data: cachedResponse.data,
                userInfo: cachedResponse.userInfo,
                storagePolicy: cachedResponse.storagePolicy)

            return modifiedCachedResponse
        }
    }

    private func urlSessionConfiguration() -> NSURLSessionConfiguration{
        let urlSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
        urlSessionConfiguration.requestCachePolicy = cachePolicy
        urlSessionConfiguration.timeoutIntervalForResource = timeOut
        urlSessionConfiguration.timeoutIntervalForRequest = timeOut
        return urlSessionConfiguration
    }

    func logOperation<T>(response : Response<T,NSError>){
        if(self.debugMode){
            print(response.debugDescription)
        }
    }
}

Our router class:

import Alamofire
import Foundation


enum CMSRouter: URLRequestConvertible {
    static let hostURL = NSURL(string: NSUserDefaults.standardUserDefaults().objectForKey(NSUserDefaults.SettingsKeys.CMS, forceLoadSettings: true) as! String)!

    static let baseURL = NSURL(string: "/base/url",relativeToURL: hostURL)


    var URL : NSURL { return CMSRouter.baseURL!.URLByAppendingPathComponent(route.path) }


    // Router Cases
    case ActiveStores
    case EmailDomain
    case Languages(String)
    case FieldControlDisplay(String)
    case Texts(String,String)
    case Titles(String,String)
    case Countries(String,String)
    case Nationalities(String,String)
    case AddressFieldsOrder(String,String)
    case ClientType

    // MARK: Route from cases

    var route : (path: String, parameters: [String: AnyObject]?) {
        switch self{
        case .ActiveStores:
            return ("/api1",nil)
        case .EmailDomain:
            return ("/api2",nil)
        case .Languages(let zoneCode):
            return ("/api3/\(zoneCode)",nil)
        case .FieldControlDisplay(let zoneCode):
            return ("/api4/\(zoneCode)",nil)
        case .Texts(let zoneCode, let lang):
            return ("/api5/\(zoneCode)/\(lang)",nil)
        case .Titles(let zoneCode, let lang):
            return ("/api6/\(zoneCode)/\(lang)",nil)
        case .Countries(let zoneCode, let lang):
            return ("/api7/\(zoneCode)/\(lang)",nil)
        case .Nationalities(let zoneCode, let lang):
            return ("/api8/\(zoneCode)/\(lang)",nil)
        case .AddressFieldsOrder(let zoneCode, let lang):
            return ("/api9/\(zoneCode)/\(lang)",nil)
        case .ClientType:
            return ("/api10/",nil)

        }

    }

    var URLRequest: NSMutableURLRequest {
        return
            Alamofire
                .ParameterEncoding
                .URL
                .encode(NSURLRequest(URL: URL), parameters: route.parameters).0
    }
}

And the service child which implement the functions we are calling:

import Foundation
import Alamofire

class MyCCCMSHTTPService: MyCCHTTPService, MyCCCMSService {

    // MARK: configuration/stores/active
    func fetchStoresActive(completionHandler : CountryStores -> Void, failureHandler : NSError -> Void) -> Void{
        self.manager!.request(CMSRouter.ActiveStores)
            .responseModelRocketJSON { (response : Response<CountryStores,NSError>) -> Void in
                self.logOperation(response)
                if(response.result.error != nil){
                    failureHandler(response.result.error!)
                }
                else{
                    completionHandler(response.result.value!)
                }
        };
    }

so the problem is that self.manager?.delegate.dataTaskWillCacheResponse is never used on iOS8...
I got nothing to specify to use cache, and receiving nothing in the response header. For iOS 9 it's works perfectly :/

Thanks for all the info you provided here.

We have a fairly large set of CacheTests to help verify this type of behavior. If you open up the test suite and run this test on iOS 8.x or iOS 9.x and set a breakpoint in the URLSession(_:dataTask:willCacheResponse:completionHandler:) method, you'll see that the breakpoint is being called each time.

I then added your delegate.dataTaskWillCacheResponse override to the test's manager and re-ran. Both the delegate method and the override closure are being called as expected.

The only difference may be that I'm running Alamofire 3.3.1 and you are running 3.2.1. We have made some changes in the 3.3.1 release that fixed some bugs around some of the delegate methods being called, but this particular session delegate method should not have been affected. Additionally, the bugs there should have only affected the 3.3.0 release, not the 3.2.1 release. Either way, I'd encourage you to dig into the tests that I've linked in this comment to see the behavior working for yourself. Then I would update to 3.3.1 and see if that fixes your issue.

If 3.3.1 does not fix your issue, then you'll need to figure out why your logic is different than the logic in the tests. I don't see anything obvious from the code you posted, but that doesn't mean that you don't have something else set that is affecting the behavior that isn't in your example.

At this point, I don't see an issue in Alamofire. All the behavior you've reported is not working seems to be working correctly from what I can tell. Because of this, I'm going to close this issue out for now. If you do still continue to have issues, then please provide a failing test or sample project for us and attach it to this issue. Then we'll gladly re-open the issue and continue to help you investigate further.

Best of luck! 馃嵒

Hello, thanks for the link to tests, really great

I tried and check a lot with the test to so what the problem was. The test are working great with iOS 8 and 9 so it's not coming from here. So i just the problem is coming from the fact that my response has nothing about cache in the headers
Since i can't touch the server i did a fix by stocking in the NSSharedCache manually the response and it's working ^^

Cool...thanks for the update!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dpstart picture dpstart  路  3Comments

shivang2902 picture shivang2902  路  3Comments

tib picture tib  路  3Comments

zhouhao27 picture zhouhao27  路  3Comments

solomon23 picture solomon23  路  3Comments