Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLCache providing inconsistent results on iOS5, seemingly at random

I've just spent an entirely too long amount of time screaming my head off at NSURLCache, so I offer this bit of advice in the hope that others can avoid my misfortune.

It all started off reasonably enough. My new app project only targets iOS 5 and above, so I thought I could take advantage of the new NSURLCache implementation for all my web caching needs. I needed a custom subclass of NSURLCache to handle a few special tasks, but that all seemed to be helpfully supported by the API. A quick read of the documentation, and I'm off to the races:

[NSURLCache setSharedURLCache:[[MyCustomCache alloc] initWithMemoryCapacity:8 * 1024 * 1024 //8mb 
                                                                    diskCapacity:32 * 1024 * 1024 // 32mb 
                                                                        diskPath:@"webcache.db"]];

I figure an 8mb cache is fine to start, and I'll back it with a larger disk cache so we can serve more of our larger images locally. I hook up the rest of my network code to use NSURLConnection (actually, I used MKNetworkKit, but that turned out to be irrelevant), and expect great things from my cache. Sure enough, all the requests that are supposed to be cached are dutifully getting saved to the cache, and responses are dutifully flying back quickly when they are served from the cache. It's a regular production of Pirates of Penzance with so much duty flying around in my networking stack.

Except something's not adding up. Requests that can be served from the cache are still going out over the network. Except when they aren't. It appears totally random and intermittent whether the cache is actually used to serve a request. I tear out my hair in frustration and dig through literally everything trying to figure out what's going on. I build test apps, set breakpoints all over the place, tear through packet traces, read every word on the internet that mentions NSURLCache, experiment with cache-control headers, comment out code, bypass my subclass, and even resort to painstakingly tracing through the assembly for NSURLCache and its CFNetworking friends to try to understand what mysterious logic lies beneath. I massively improve my knowledge of ARM and Objective-C calling conventions and learn a fair bit about low-level debugging, but get nowhere in actually figuring out what's going on. The whole thing is feeling a lot more like Iolanthe's Nightmare Song than the benign dictatorship of the Pirate King and I'm pretty much on the verge of throwing it all away.

TL/DR version: NSURLCache appears to be working, but randomly doesn't return the cached results even though it has them available.

like image 559
Zach Lipton Avatar asked Jul 16 '12 03:07

Zach Lipton


2 Answers

Eventually, I try a different permutation of all the bits I've been fiddling with. I set both the memory and disk cache sizes to 8mb.

Lo and behold, all the weirdness goes away! Everything that's supposed to be cached is getting saved. And everything that is supposed to come from the cache is getting served without network requests.

It seems that the NSURLCache implementation in iOS5 still isn't complete. It does use the disk and memory caches (unlike iOS4 and earlier, which only implemented the in-memory cache), but it doesn't actually pass through the memory cache to the disk cache when a request misses. As such, it's basically blind luck (well, blind luck influenced by all your other network and cache usage) whether a given response happens to be in memory or not at the right instant. This is probably useful to reduce flash memory file IO on the device, but insanely obnoxious if you expect rational behavior from the class.

And so with laughing song, and merry dance, I check in my two-line fix and make haste for the bar, eventually sharing this knowledge with SO (and an Apple bug report) in the hope that no one else has to go through this pain again.

The moral of this tale of woe: strange and evil things will happen if you try to use NSURLCache on iOS5 with a disk capacity larger than the memory capacity. Don't do this. And avoid making enemies of magical fairies.

like image 180
Zach Lipton Avatar answered Jan 03 '23 11:01

Zach Lipton


FWIW here's my final solution:

https://gist.github.com/3245415

it requires using FMDB, but the results are pretty good.

like image 25
bogardon Avatar answered Jan 03 '23 11:01

bogardon