NSURLCache Alamofire removing cached responses - ios

I'm using the following code to test a behavior in NSURLCache. I initialize an API instance in AppDelegate. I configure the manager according to Alamofire's documentation, I configure the shared cache, and I assign dataTaskWillCacheResponse to make sure that the response will indeed be cached.
Then I call makeRequest which checks if a cached response exists (which it shouldn't on the first launch) and then I use my manager to make a request using the same URL so that the request is equivalent throughout the test.
My breakpoint at dataTaskWillCacheResponse is hit, I continue, the responseJSON block is executed and is Successful so I performTests using the request.
First, I check if the response is cached. It is: good!
Second, (and this is the problem) I remove the cached response for that request and then check if it exists. It does: bad!
Third, I check if removing all cached responses will remove that response. It does: good! But it's odd that that worked and the previous attempt at just removing the single response didn't...
Here's the code:
import Alamofire
class API: Manager.SessionDelegate {
var manager: Manager!
override init() {
super.init()
manager = Manager(session: urlSession(), delegate: self)
configureCache(memoryCapacityMB: 5, diskCapacityMB: 25)
manager.delegate.dataTaskWillCacheResponse = { urlSession, dataTask, cachedResponse in
// Placing a breakpoint here confirms that the response is going to be cached
return cachedResponse
}
}
private func urlSession() -> NSURLSession {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
return NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
private func configureCache(memoryCapacityMB memory: Int, diskCapacityMB disk: Int) {
let memoryCapacity = memory * 1024 * 1024
let diskCapacity = disk * 1024 * 1024
let sharedCache = NSURLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: nil)
NSURLCache.setSharedURLCache(sharedCache)
}
// MARK: Request
func makeRequest() {
// The response should be nil on the first launch since nothing has been cached
let request = NSURLRequest(URL: NSURL(string: "http://jsonplaceholder.typicode.com/posts")!)
let response = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
print(response)
manager.request(.GET, request.URLString).responseJSON { response in
switch response.result {
case .Success:
self.performTests(with: response.request!)
case .Failure:
break
}
}
}
func performTests(with request: NSURLRequest) {
// Should exist
var response = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
print(response)
// And it does: good!
// Remove the cached resopnse and check if it exists
NSURLCache.sharedURLCache().removeCachedResponseForRequest(request)
response = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
print(response)
// And it does: bad!
// Try removing all cached responses and check if it exists
NSURLCache.sharedURLCache().removeAllCachedResponses()
response = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
print(response)
// And it doesn't: good! But odd...
}
}
So how does one remove the cached response of a single request then? And is this unintended behavior? Or is NSURLCache behaving correctly and I'm just missing something? Thanks ahead of time for taking a look!

My recollection is that most URL cache changes are not synchronous. They only actually happen after you return to the run loop and allow various asynchronous callbacks to occur.
Try running the rest of the code asynchronously after a delay of 3-5 seconds and see if the request has been removed.
If that doesn't fix the problem, file a bug.

Related

NSURLCache using cache for requests older than max-age

I've been banging my head against the while now trying to figure out what's going on with NSURLCache.
Basically, the server I am connecting to doesn't set any cache control headers... So following various guides and apple docs (ie https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Concepts/CachePolicies.html) I set my own cache control headers in the willCacheResponse delegate and then return that modified response in the completion handler. The apple docs and the resource I've read seem to indicate this should work. But what I'm seeing is that the cached data is returned after it should be expired based on max-age. It seems like max-age is being ignored and the NSURLCache is using another heuristic to determine if it should pull data from the cache or not.
I set the max-age=60 cache-control header and I've verified using Charles that the data is pulled from cache and no network requests are made long after 60 seconds. Eventually (seems non-deterministic) a new request will be made that actually goes to the server (usually after a few hours have passed and I try the request again).
Here is the code, for testing purposes I'm just hardcoding the max-age to 60 seconds:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: #escaping (CachedURLResponse?) -> Void) {
var modifiedReponse: URLResponse? = nil
if let HTTPResponse = proposedResponse.response as? HTTPURLResponse {
if var newHeaders = HTTPResponse.allHeaderFields as? [String : String] {
if newHeaders["Cache-Control"] == nil {
newHeaders["Cache-Control"] = "max-age=60"
}
modifiedReponse = HTTPURLResponse(url: HTTPResponse.url!, statusCode: HTTPResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: newHeaders)
}
}
let response = modifiedReponse ?? proposedResponse.response
var newCachedResponse: CachedURLResponse? = nil
newCachedResponse = CachedURLResponse(response: response, data: proposedResponse.data, storagePolicy: proposedResponse.storagePolicy)
}
There are a few optional checks in there but I have confirmed that the response on newCachedResponse I am returning has the cache control header set to max-age=60. Am I doing something obviously wrong here? Or is NSURLCache just F'd?
I know this is super late, but I think you just need to pass that newCachedResponse back into the completion handler closure provided to you.
Such as the last line being:
completionHandler(newCachedResponse)

How to make sure that NSURLSession responses are received in same order as requests are made?

I make few NSURLSession requests in a loop. I'd like to store results from responses in the same order as tasks are created. But since completion handler runs in a separate thread it sometimes happens that the response to the second task gets received before the response to the first task.
How to make sure that I get responses in same order as tasks are being started?
var recivedData = [String]()
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData.append("\(result)") // save the result to array
}
}
task.resume()
}
While I'd discourage behaviour that requires responses to be received in a specific order, you can collate the responses (regardless of the order they are received) into a known order.
The receivedData array should be initialised with a capacity that matches the number of requests that will be made:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
Then when you receive the response, since you're in a block that has access to the index of the request you can add the response data directly into the index of the receivedData array:
receivedData[index] = result as (String)
The full code is as follows:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
// Insert the result into the correct index
receivedData[index] = result
}
}
task.resume()
}
Because you know the exact number of http requests.
You can create an array to the size of urls.count, and then set the result in completion handler, corresponding to the index in each loop.
receivedData = [String](count: urls.count, repeatedValue: "No Data")
for (index,url) in enumerate(urls){
let url = NSURL(string: url)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url){ data, response, error in
if error != nil{
self.receivedData[index] = "error: \(error.localizedDescription)"
return
}
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData[index] = "\(result)"
}
}
task.resume()
}
Actually, can not make sure the order of responses.
There are two workarounds for this:
Send the requests one after another, which means send the next request after the previous response is returned. You can use ReactiveCocoa to make your codes elegant. Or use the networking library I wrote STNetTaskQueue, there is a STNetTaskChain which can handle the requests one after another.
Send the requests parallel(is what you are doing now), and use a NSDictionary to keep track on the request and response, after all requests is finished, combine the response in the original order.
Hope this helps!

Running a url using swift

I've created a script/api which is suppose to add a record to my database when running a specific url. However i'm not sure how to run this url. I do not expect anything back just to run this url? how can i do this?
var identifier = UIDevice.currentDevice().identifierForVendor.UUIDString
var addViewUrl = "http://url/addview.php?type=ios&identifier=\(identifier)&newsid=\(newsObject?.id)"
Based on my comment:
You should get a response and check for errors.
Also there is always the possibility to call a URL asynchronously to avoid blocking the GUI if the request takes a long time.
This can be made using delegate patterns or with completions handlers like in Objective-C.
Example:
var url = NSURL.URLWithString(addViewUrl)// Creating URL
var request = NSURLRequest(URL: url)// Creating Http Request
var queue: NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{(response:NSURLResponse!, responseData:NSData!, error: NSError!) -> Void in
if error != nil
{
println(error.description)
}
else
{
var responseStr:NSString = NSString(data:responseData, encoding:NSUTF8StringEncoding)
//Everything went fine
}
})

Checking HTTP Status code without cache

I need to check if a website is reachable before loading it. I am new in iOS developement but this is the method I've implemented to discover the response.
var url = NSURL(string: "http://www.apple.com")
var task = NSURLSession.sharedSession().dataTaskWithURL(url!) {
data, response, error in
println(data)
var httpResponse = response as? NSHTTPURLResponse
println(httpResponse)
}
task.resume()
It works! But the problem is that the response comes from the cache... So the result is that:
If I am checking if a file exists and at that moment I am checking it exists -> for the application it will always exist because it is stored in the cache... So if I remove the file and then I make the request... it will always give me response 200 and not 404.
Infact if I insert this line of code (it deletes the cache!) before making the request then it works like it should work and it always check for real if the website or the file exists!
NSURLCache.sharedURLCache().removeAllCachedResponses()
So... how can I solve this problem in Swift?...thank you very much
You can set a no cache policy by using a new url session instance.
Create a property and set a new NSURLSession instance to it.
var urlSession : NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData;
self.urlSession = NSURLSession(configuration: configuration)
Use this URLSession property to get your data.
var url = NSURL(string: "http://www.apple.com")
var task = self.urlSession.dataTaskWithURL(url!) {
data, response, error in
// Your code
}
task.resume()

NSURLCache's removeCachedResponseForRequest has no effect

I've created an NSURLCache subclass which forces caching of responses for a designated amount of time. This is working well, and cached items are expired as expected. However I'm running into issues when trying to forcefully remove a cached response using NSURLCache's removeCachedResponseForRequest: method.
What I'm looking to achieve is to allow users to force an immediate reload of remote data. To do this, I pass an "ignoreCache" flag when making a request. I construct my NSURLRequest as normal, but ask my NSURLCache to remove any previously cached responses for the given request. This doesn't seem to have any effect, however, as the cached result is still present and used when the request is executed.
The documentation around NSURLCache is fairly sparse. The NSURLCache headers state that the NSURLRequest passed to removeCachedResponseForRequest: is used as a key to lookup the associated NSCachedURLResponse object, but there's little information given as to the logistics of that comparison. Does the NSURLCache class expect to receive the same NSURLRequest instance that generated the cached response, or does it simply compare the NSURL it represents?
Hopefully someone can point me in the right direction.
var error: NSError? = nil
var URLRequest: NSMutableURLRequest = self.operationManager.requestSerializer.requestWithMethod("GET", URLString: NSURL(string: request.URL, relativeToURL: self.operationManager.baseURL).absoluteString, parameters: request.parameters, error: &error)
URLRequest.cachePolicy = NSURLRequestCachePolicy.ReturnCacheDataElseLoad
// We've been asked to ignore the cache, so remove our previously cached response
if ignoreCache == true {
NSURLCache.sharedURLCache().removeCachedResponseForRequest(URLRequest)
}
Here's the Swift code from my NSURLCache subclass for reference:
// MARK: - NSURLCache
override func cachedResponseForRequest(request: NSURLRequest!) -> NSCachedURLResponse! {
var cachedResponse: NSCachedURLResponse? = super.cachedResponseForRequest(request)
if(cachedResponse != nil && cachedResponse!.userInfo != nil) {
var cacheDate: NSDate? = cachedResponse!.userInfo![self.cacheExpirationKey] as? NSDate
if(cacheDate != nil) {
var cacheDateLimit: NSDate = cacheDate!.dateByAddingTimeInterval(self.cacheExpirationInterval)
if(cacheDate!.compare(NSDate()) == NSComparisonResult.OrderedAscending) {
self.removeCachedResponseForRequest(request)
} else {
return cachedResponse
}
}
}
// Either our cached data was too old, or we don't have any that match the NSURLRequest
return nil
}
override func storeCachedResponse(cachedResponse: NSCachedURLResponse!, forRequest request: NSURLRequest!) {
var userInfo: NSMutableDictionary = NSMutableDictionary(dictionary: cachedResponse.userInfo)
userInfo[cacheExpirationKey] = NSDate().dateByAddingTimeInterval(self.cacheExpirationInterval)
var modifiedCacheResponse: NSCachedURLResponse = NSCachedURLResponse(response: cachedResponse.response, data: cachedResponse.data, userInfo: userInfo, storagePolicy: cachedResponse.storagePolicy)
super.storeCachedResponse(modifiedCacheResponse, forRequest: request)
}
Assuming you are on iOS 8, removeCachedResponseForRequest: does not work.
See http://blog.airsource.co.uk/2014/10/13/nsurlcache-ios8-broken-2/

Resources