19

I can't find any answer to my question, may be I miss something...

When I ask for a url, I need to know if the response comes from the cache or from the net.

Is the status code 304 or 200 ? (but AFNetworking always responde 200)

With ASIHTTPRequest I used to check "didUseCachedResponse" from ASIHTTPRequest, this was perfect.

elp
  • 8,021
  • 7
  • 61
  • 120
Vassily
  • 899
  • 2
  • 8
  • 19
  • I know this is not what you are asking, but if you specifically wanted only a cache or only NOT a cache you should check out the `NSURLRequestCachePolicy` property of the request. – James Paolantonio Aug 29 '12 at 14:55
  • Thanks, CachePolicy seems well set in my code since the response comes from cache when I ask a second time the same url. The probleme is that I don't know how to check that within the code. If the response comes from the cache, I'd like not to do some extra heavy code. – Vassily Aug 29 '12 at 15:03
  • seems that apple doesn't want you to know if it comes from cache or not. I found a way by saving modification-date associate with the request, and I compare this date when AFNetWorking answers to me. not as clean as I intend, but works... – Vassily Sep 03 '12 at 08:06
  • Is there an updated answer for this question for `AFNetworking 3.x`? – fatuhoku Feb 10 '16 at 13:30

4 Answers4

24

I think I found a solution to determine if response was returned from cache or not using AFNetworking 2.0. I found out that each time a new response is returned from the server (status 200, not 304) the cacheResponseBlock which is a property of AFHTTPRequestOperation is called. The block should return NSCachedURLResponse if response should be cached or nil if it shouldn't. That's way you can filter responses and cache only some of them. In this case, I am caching all responses that comes from the server. The trick is, that when server sends 304 and response is loaded from cache, this block won't be called. So, this is the code I am using:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

BOOL __block responseFromCache = YES; // yes by default

void (^requestSuccessBlock)(AFHTTPRequestOperation *operation, id responseObject) = ^(AFHTTPRequestOperation *operation, id responseObject) {
    if (responseFromCache) {
        // response was returned from cache
        NSLog(@"RESPONSE FROM CACHE: %@", responseObject);
    }
    else {
        // response was returned from the server, not from cache
        NSLog(@"RESPONSE: %@", responseObject);
    }
};

void (^requestFailureBlock)(AFHTTPRequestOperation *operation, NSError *error) = ^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"ERROR: %@", error);
};

AFHTTPRequestOperation *operation = [manager GET:@"http://example.com/"
                                      parameters:nil
                                         success:requestSuccessBlock
                                         failure:requestFailureBlock];

[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
    // this will be called whenever server returns status code 200, not 304
    responseFromCache = NO;
    return cachedResponse;
}];

This solution works for me and I haven't found any issues so far. But, if you have a better idea or some objections against my solution, feel free to comment!

Wayne
  • 59,728
  • 15
  • 131
  • 126
Darrarski
  • 3,882
  • 6
  • 37
  • 59
  • I up this response without testing it, but it looks like a clean workaround. Thanks – Vassily Feb 06 '14 at 13:45
  • 1
    Currently using this in app that does heavy networking. It's in development stage, but after more and more testing I still can't find any issues. It just works. – Darrarski Feb 06 '14 at 17:19
  • 4
    You have to be careful because this block isn't called when the server has no or improper cache response headers. If that's the case, you would always think that AFNetworking is loading from cache but indeed it is loading from the server. There are some other circumstances when this method will not being called – see apple doc: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionDataDelegate_protocol/Reference/Reference.html#//apple_ref/occ/intfm/NSURLSessionDataDelegate/URLSession:dataTask:willCacheResponse:completionHandler: – Alexander Jul 17 '14 at 06:50
  • This did not work for LARGE requests (on OS X) for me until I altered the NSURLCache disk and memory size limits. I do not know the exact requirements but a 1.6MB response would never call the response block with defaults. – dbainbridge Jan 23 '15 at 17:49
  • 2
    For `AFURLSessionManager`, it is `dataTaskWillCacheResponse` – onmyway133 Jun 04 '15 at 15:26
  • @Darrarski you have anyway for without internet got data form cache ? – Darshan Kunjadiya Jul 29 '15 at 12:48
  • @onmyway133 Can you please elaborate on this ? I cannot figure out how to find whether a cache is being used or not using AFURLSessionManager – Petar Sep 03 '15 at 08:51
  • This definitely not work with AFURLSessionManager. `setDataTaskWillCacheResponseBlock` is the function that let you decide whether cache it or not. – JZAU Dec 06 '15 at 23:25
2

seems that apple doesn't want you to know if it comes from cache or not.

I found a way by saving modification-date associate with the request, and I compare this date when AFNetWorking answers to me.

not as clean as I intend, but works...

Vassily
  • 899
  • 2
  • 8
  • 19
  • Check out my solution, it works for me and you don't need to store any extra data or subclass anything or even add a category. – Darrarski Feb 04 '14 at 15:07
1

There is a way to specify the status codes that should be treated as success in AFNetworking, it is done through response serialization, here is the code

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

AFHTTPResponseSerializer *respSerializer = [AFHTTPResponseSerializer serializer];
NSMutableIndexSet *responseCodes = [NSMutableIndexSet indexSet];
[responseCodes addIndex:200];
[responseCodes addIndex:304];

[operation setResponseSerializer:respSerializer];

With this code AFNetworking will treat 304 as success

G_K
  • 51
  • 4
1

Create your URLCache class and override storeCachedResponse method

class MyURLCache: URLCache {
    override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) {

        //adding caching header if needed
        var headers = response.allHeaderFields
        headers.removeValue(forKey: "Cache-Control")
        headers["Cache-Control"] = "max-age=\(5 * 60)" //5 min

        //the trick
        if (headers["isCachedReponse"] == nil){
            headers["isCachedReponse"] = "true"
        }

        if let
            headers = headers as? [String: String],
            let newHTTPURLResponse = HTTPURLResponse(url: response.url!, statusCode: response.statusCode, httpVersion: "HTTP/1.1", headerFields: headers) {
            let newCachedResponse = CachedURLResponse(response: newHTTPURLResponse, data: cachedResponse.data)
            super.storeCachedResponse(newCachedResponse, for: request)
        }
    }
}

Set URLCache.shared with your URLCache in AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        let cache = MyURLCache(memoryCapacity: 1024 * 1024 * 500, diskCapacity: 1024 * 1024 * 500, diskPath: nil)
        URLCache.shared = cache
        return true
    }
}

In response callback check if headers of response contents "newResponse" key

if (response.allHeaderFields["isCachedReponse"] == nil){
      print("not cache")
} else {
      print("cache")
}

Works for all version of AFNetworking

phnmnn
  • 12,813
  • 11
  • 47
  • 64