Peter Steinberger

NSURLCache Uses a Disk-cache as of iOS5

While writing AFDownloadRequestOperation, a new subclass for AFNetworking I discovered that the behavior of NSURLCache changed between iOS 4.x and iOS 5.x.

Before iOS5, NSURLCache just saved requests to memory, even if the documentation said otherwise - the diskCapacity property was silently ignored. This led to some open source subclasses of NSURLCache, that retrofit disc caching. Most popular is SDURLCache and my enhanced, faster fork of it. Even Apple has an example online that shows how to create a simple URLCache.

As of iOS5, NSURLCache automatically saves responses to disk. I haven’t found anything the release notes that confirms the change, but I tried it both with iOS 4.3/5.0/5.1 on the simulator and the device, and with every 5.x version a disk cache file is created and populated. This is great, as many developers probably aren’t aware of this and the system just does the right thing - and it’s fast.

If the Cache-Control headers indicate that this request can be cached, iOS automatically saves it to a local sqlite cache file in AppDirectory/Caches/(bundleid)/Cache.db. E.g. public, max-age=31536000 marks that the request cache will be valid for a year, as max-age is listed in seconds.

The sqlite scheme for the cache looks identical with the one used in OS X:

So, why should you care? Well, the Cache.db caches any file that has a correct Cache-Control header set. Thus, if you download a pdf document, it might end up in your disk cache as well, taking up twice the memory.

The default NSURLCache will be used, with a disk limit of 20MB. You can easily test this with gdb/lldb:

1
p (int)[[NSURLCache sharedURLCache] diskCapacity]

The memoryCapacity defaults to 40MB, although the cache will clear itself in low memory situations.

So for downloads that you manually save to disk, you might want to override the NSURLConnection delegate connection:willCacheResponse: and return nil.

With creating NSURLRequest using requestWithURL:cachePolicy:timeoutInterval:, you can define the cachePolicy, but this only allows you to choose how if and how the cache will be read.

Available options are: only use the cache value (NSURLRequestReturnCacheDataDontLoad), try the cache and load if different (NSURLRequestReturnCacheDataElseLoad) or ignore cache entirely (NSURLRequestReloadIgnoringLocalCacheData).

The default option, if not set explicitly, is NSURLRequestUseProtocolCachePolicy, which most of the time is equivalent to NSURLRequestReturnCacheDataElseLoad, thus uses the cache if the object hasn’t changed. There are a few other options in the enum, but those are unimplemented.

Note: There doesn’t seem a way to force caching of certain requests, connection:willCacheResponse: is only called if the response contains a Cache-Control header, according to Apple’s documentation:

The delegate receives connection:willCacheResponse: messages only for protocols that support caching.

Lastly, Apple suggests that caching can also be fine-tuned with subclassing NSURLProtocol, which indeed allows some interesting use-cases like providing a cache for UIWebView or decrypting files on the fly.

If you’re not yet using AFNetworking, you really should. It’s a big step forward compared to classical NSURLConnection handling, even if Apple recently added a few new fancy shorthands in iOS5. In AFNetworking, your network operations are indeed subclasses of NSOperation, which allows a much better control what’s currently running, and AFHTTPClient is the perfect base class to implement any API.

Moving on… Or How Waiting for a Visa Was the Best Thing Ever Happened to Me.

A few days ago I booked my flight back to Austria. Starting with April 13, I’m a full-time indie again. You gonna say, what! You’re leaving the Bay Area? This is sad! But I’m very excited about it.

I’m in a better situation than ever. I finally figured out what I wanna do with my life. I’m no longer enviously looking at other places. I learned what’s really important. And that you can’t just replace life-long friends.

Don’t get me wrong. I’m gonna miss San Francisco. Really. Such a great city. Such an awesome spirit; unlike any other city I’ve ever been to. And heck, half of my Twitter followers are here, so super-nice to finally meet up with you guys!

So why leave at all? Well, it just didn’t work out. I’m full of ideas and really, really wanna work on my own stuff.

Back when I got my job offer at a WWDC party in 2011, I was in a whole different position, worked. as. a. freelancer. for some nice companies, but wanted a change. So the prospect of living in San Francisco, being part of the startup culture, working together on something great, was very, very appealing. (damn you, Hacker News!)

So, I accepted the offer and waited for my visa. Waited, and waited. I stopped doing freelance work because initially we thought it’d all be done in a month. Ha, how wrong we were. In the end it took more than 6 month! I even finished my bachelors degree during that time.

Not only that. Suddenly my mind was free from all the freelance work (and the feeling of guilt when I didn’t work on them). Naturally, I filled that space up with other. projects. And, partly inspired by some friends, I wanted to try the paid component business. So this happened. In the end, while waiting for my visa, I’ve created a viable business that’s now making more money than my full-time job possibly ever could.

All gears where set for SF, so I took the gig, it was all about the experience now. I really believed I could do this. But then, managing a 40h+ job with basically another full-time job on the side is nearly impossible, and after killing myself for a while, I finally had to choose which is more fun. Well, let’s say it wasn’t a hard decision. (To be correct, it was very hard. I hate letting people down, and of course while they’re a business, I also respected them, and had a hard time to disappoint their high expectations they had in me.)

This all sounds so simple and logical now, but I really jumped into the whole business and landed on my feet. Support requests and ideas are exploding, and as revenue goes up, so does the amount of time to care about my product. Also, I was really looking forward to work wit some well-respected people in the iOS scene, only to find out that they had already left the company before I came here.

Lastly, NSConference gave me the final kick. Such an amazingly great amount of inspiring people that genuinely love what they do - you can’t just go back to your 11-7 job after experiencing that. Also, meeting customers that use your product and really love it is the sweetest thing ever.

I have no regrets on trying it, and I’m very thankful for the opportunity they’ve given me - in fact it’s one of the best things ever happened to me! It gave me the guts to go full-indie, without even thinking about it. It helped me to figure out what I really wanna do with my life.

So, what’s next? Well, I’ve big plans with making PSPDFKit even better… and then there’s this other app idea, that’s crossing my mind way too often. I’m building this for myself, because I really, really want it. And every time I did that, it turned out to be a great success. If you wanna hear more about it, you should follow me on Twitter.

And about San Francisco, I’ll probably be back pretty soon ;)

Don’t Call willChangeValueForKey Unless It’s Really Needed

You’re using KVO, right? So you most likely already have written code like this:

Carefully encapsulating your calls within a willChangeValueForKey/didChangeValueForKey call to “enable” KVO. Well, turns out, you just did shit work. There’s no reason to do the will/did dance, as long as you are using a setter method to change the ivar. It doesn’t need to be backed by an ivar or declared as a property. KVO was written long before there was anything like @property in Objective-C.

In fact, I am now writing iOS apps for about 4 years and didn’t know this. A lot. of. open. source. code. also gets this wrong. Apple has some good KVO documentation, where they explain the concept of Automatic Change Notifications.

There is a reason why you want to manually add willChangeValueForKey, most likely changing a variable also affects another variables. The most popular example is NSOperation:

Sometimes you also might wanna optimize how often you’re sending KVO notifications with overriding automaticallyNotifiesObserversForKey: to disable automatic change notifications for certain properties.

In this example, there might be expensive KVO observations when the image changes, so we wanna make damn sure that KVO is only fired if the image actually is a different one to the already set image:

If you currently have such obsolete calls, they’re not doing any harm. Incrementally called willChangeValueForKey doesn’t emit more notifications. Still, time to delete some code!

Update: Don’t forget that there are more ways that’ll save you manual calls to will/did, like using the little known addition keyPathsForValuesAffecting(PropertyName), which uses some runtime magic to make KVO smarter. Here’s a real-life example how I used that on AFNetworking, so people can register a KVO notification on “isNetworkActivityIndicatorVisible” and it’ll get sent every time activityCount is changed. (You’ll also see that I do some atomic updating that requires manual KVO.)

Reboot

I’ve rebooted my blog. Will add random bits about iOS development and some personal thoughts, especially since some of my friends complained that apparently I tweet too much (cough Amy cough :) And they’re right. I love twitter, but sometimes you just need more than 140 characters.

The old content has been moved into limbo. Most of it would have needed some update to be valid again, so you’re not missing a lot. So if you’re coming from Google and miss a particular thing, hit me up on Twitter and I might dig it up for you.