Last Updated: February 25, 2016
·
3.532K
· lucabernardi

Cache NSDateFormatter

A couple of days ago I was playing with a JSON API. The response of this API is a long list of items where every item is composed with a bunch of field and a timestamp. In the model class for this item I want to expose the timestamp as NSDate property, in order to do so I've to convert it from the NSString representation to NSDate with the help of NSDateFormatter. The code works fine but when I tested it on real device (iPhone 5) it takes a couple of seconds to execute the task (I easily notice it because I was doing my experiment on the main thread), with the help of Instrument I easily figure out that the problem was relate to NSDateFormatter.

[NSDateFormatter init] is a quite expensive operation. If you have to perform it many times, like in my case, when you parse a lot of item it can be a performance issue. A good idea is to cache a single instance and reuse it as proposed by Apple in Cache Formatters for Efficiency where they suggest the use of a static variable to hold the shared instance, as you would in the case of a singleton.

This solution is simple and works very well except in case of multithreading. NSDateFormatter is not thread safe and quoting Apple: "you must not mutate a given date formatter simultaneously from multiple threads"

For this reason really bad things can happen to you if you try to access it from multiple thread. A better solution is the one proposed in Threadsafe Date Formatting with the use of Thread-Local Storage.

Here the code:

NSString * const kCachedDateFormatterKey = @"CachedDateFormatterKey";

+ (NSDateFormatter *)dateFormatter
{
    NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
    NSDateFormatter *dateFormatter = [threadDictionary objectForKey:kCachedDateFormatterKey];
    if (!dateFormatter) {
        dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

        [dateFormatter setLocale:enUSPOSIXLocale];
        [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSS"];

        [threadDictionary setObject:dateFormatter forKey:kCachedDateFormatterKey];
    }
    return dateFormatter;
}

A gotcha of this code is that the method will return always the same instance if called in the same thread, if it's called in a new and different thread a new instance is created and returned.

1 Response
Add your response

Awesome Post. Thanks

over 1 year ago ·