Be wary of -[NSCalendar currentCalendar]

Update: David Smith informs me that as of iOS 7 and OS X 10.9, +[NSCalendar currentCalendar] is cached, thanks to NSCalendar now being thread safe. Thus most of this post is now moot.

I was recently tasked with tuning the performance of an app which was doing a lot of calendrical calculation. A few samples with Instruments/Shark clearly showed that the bulk of time was indeed spent in various NSCalendar methods, performing calculations on dates. So, clearly I needed to figure out a way to optimise the code to perform less of these calls, right? Wrong.

After a little more poking around, I noticed something suspicious. Inside the majority of the NSCalendar calls, the stack trace would go something like this:

…before moving on to do more work at the CoreFoundation-level for a briefer time. 'Well that seems kinda suspicious' I said to myself, 'why is the application spending so much time in a function labeled "setup"?'

A quick bit of Googling showed nothing useful for those private CoreFoundation functions, but ucal_open is properly documented. From this I can deduce that internally each NSCalendar instance uses a UCalendar (from the ICU library) to perform its calculations. This is lazily setup the first time the calendar is used. Therefore, it would be wise not to create more NSCalendar instances than you need (in most apps, probably only one!).

Where are all these calendars coming from then? Well, the code was generally just following this pattern:

[[NSCalendar currentCalendar] fooBar];

Convenient, but in practice not very performant. Presently, +[NSCalendar currentCalendar] appears to return a new object every time. Which, as we saw above, is bad. This is hard to spot form sampling as the method itself is very fast; it's the later results that are expensive! How to fix it? Some options:

A) Try using CFCalendarCopyCurrent()

This function is toll-free bridged with NSCalendar, but is documented to return a cached object if it feels like it. Could be an easy way to solve the problem.

B) Globally cache the calendar

If you actually don't want to consider the possibility of the calendar changing, another easy solution would be to cache it yourself:

C) Fix the design

Of course, this is the one you should really do. Any code performing a calendrical calculation should be given the calendar with which to perform the task. Either as a property, or passing it in as an argument. Look at Cocoa for example: the two classes which do perform such calculations for themselves both have a -calendar method which they then use for doing the work. Of course you're welcome in your code to have such a property default to +[NSCalendar currentCalendar].

I hope that all helps in some fashion. I wrote this post after being surprised at the lack of search results for any of the private CoreFoundation methods, so this should soon help fill a hole.

© Mike Abdullah 2007-2015