NSURLSession URL response is not cached if more than a few kilobytes

I am new to IOS programming with Cocoa and I am using Swift. I am fetching data from JSON API using NSURLSession data session with custom delegate and not closure. The reason for using a custom delegate is that I have to do basic authentication and also add a custom cache header to control caching (my API doesn't include any caching related headers in the response at all).

This all works fine, but only for requests for which the URLSession: dataTask: didReceiveData: method is called only once. As soon as I receive larger responses (about 20-30KB) that call the didReceivedData method multiple times, the URLSession: dataTask: willCacheResponse: completeHandler: method is not called at all and therefore my response is not cached. Issuing the same request again within 5 minutes will send the request to the server again, which will not happen for requests whose responses only called callReceiveData once. The URLSession: task: didCompleteWithError: method is correctly called and executed in all cases.

Documentation URLSession: dataTask: willCacheResponse: completeHandler: method ( https://developer.apple.com/library/IOs/documentation/Foundation/Reference/NSURLSessionDataDelegate_protocol/index.html#//apple_ref/occ/intfm/NSURLSateURataDataLS : willCacheResponse: completionHandler :) says that this method is only called if the NSURLProtocol request processing decides to do it, but I really don't understand what to do to make this happen.

Any feedback and ideas are greatly appreciated!

This is the code that issues the HTTP request:

    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    config.URLCache = NSURLCache.sharedURLCache()
    //config.URLCache = NSURLCache(memoryCapacity: 512000000, diskCapacity: 1000000000, diskPath: "urlCache")

    let urlString = apiUrlForFilter(filter, withMode: mode, withLimit: limit, withOffset: offset)
    let url = NSURL(string: urlString)

    var policy: NSURLRequestCachePolicy?
    if ignoreCache == true {
        policy = .ReloadIgnoringLocalCacheData
    } else {
        policy = .UseProtocolCachePolicy
    }

    let request = NSURLRequest(URL: url!, cachePolicy: policy!, timeoutInterval: 20)
    let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
    let task = session.dataTaskWithRequest(request)
    task.resume()

      

I have implemented the following delegate functions:

  • URLSession: didReceiveChallenge: completeHandler: to manage trust for SSL certificates,
  • URLSession: task: didReceiveChallenge: completeHandler: to handle basic authentication
  • URLSession: dataTask: didReceiveResponse: completeHandler: to read specific headers and store them as instance variables

Also, important with code:

URLSession: dataTask: didReceiveData: to accumulate data as large HTTP requests come in:

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
    if receivedData == nil {
        receivedData = NSMutableData()
    }
    receivedData!.appendData(data)
    println("did receive data: \(receivedData!.length) bytes")
}

      

URLSession: dataTask: willCacheResponse: completeHandler: to inject my own Cache-Control header:

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse!) -> Void) {
    println("willCacheResponse was called")

    let response: NSURLResponse = proposedResponse.response
    let httpResponse = response as NSHTTPURLResponse
    var headers = httpResponse.allHeaderFields

    var modifiedHeaders = headers        
    modifiedHeaders.updateValue("max-age=300", forKey: "Cache-Control")
    let modifiedResponse = NSHTTPURLResponse(URL: httpResponse.URL!, statusCode: httpResponse.statusCode, HTTPVersion: "HTTP/1.1", headerFields: modifiedHeaders)
    let cachedResponse = NSCachedURLResponse(response: modifiedResponse!, data: proposedResponse.data, userInfo: proposedResponse.userInfo, storagePolicy: proposedResponse.storagePolicy)
    completionHandler(cachedResponse)
}

      

URLSession: task: didCompleteWithError: to check the complete error response and trigger the close callback, this class gets by initialization to continue with the result data:

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
    session.finishTasksAndInvalidate()

    if error != nil {
        var string: String?
        if errorString != nil {
            string = errorString
        } else {
            string = Helpers.NSURLErrorDomainErrorForCode(error!.code)
        }
        errorCallback(string!)
        return
    }

    if receivedData == nil {
        errorCallback("the query returned an empty result")
        return
    }

    var jsonError: NSError?
    let results: AnyObject! = NSJSONSerialization.JSONObjectWithData(receivedData!, options: NSJSONReadingOptions.AllowFragments, error: nil)

    if results == nil {
        errorCallback("the data returned was not valid JSON")
        return
    }

    let jsonParsed = JSONValue.fromObject(results)

    if let parsedAPIError = jsonParsed!["error"]?.string {
        errorCallback("API error: \(parsedAPIError)")
        return
    }

    callback(jsonParsed!, self.serverTime!)
}

      

+3


source to share





All Articles