Is caching a NSDateformatter application-wide good idea?

Tieme picture Tieme · Dec 5, 2014 · Viewed 9k times · Source

It's well known that creating NSDateFormatters is 'expensive'

Even Apple's Data Formatting Guide (updated 2014-02) states:

Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static variable.

But that doc seems not really up to date with swift and I also can't find anything about that in the latest NSDateFormatter Class Reference about caching the formatter so I can only assume that it's just as expensive for swift as it is for objective-c.

A lot of sources suggest caching the formatter inside the class using it, for example a controller or a view.

I was wondering if it would be handy or even 'cheaper' to add a singleton class to the project to store the datepicker so you're assured it's never necessary to create it again. This could be used everywhere in the app. You could also create several shared instances containing multiple datepickers. For example one datepicker for displaying dates and one for a time notation:

class DateformatterManager {
    var formatter = NSDateFormatter()

    class var dateFormatManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        // date shown as date in some tableviews
        Static.instance.formatter.dateFormat = "yyyy-MM-dd"
        return Static.instance
    }

    class var timeFormatManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        // date shown as time in some tableviews
        Static.instance.formatter.dateFormat = "HH:mm"
        return Static.instance
    }

    // MARK: - Helpers
    func stringFromDate(date: NSDate) -> String {
        return self.formatter.stringFromDate(date)
    }
    func dateFromString(date: String) -> NSDate? {
        return self.formatter.dateFromString(date)!
    }
}

// Usage would be something like: 
DateformatterManager.dateFormatManager.dateFromString("2014-12-05")

Another likewise approach would be creating just one singleton and switching the format depending on the need:

class DateformatterManager {
    var formatter = NSDateFormatter()

    var dateFormatter : NSDateFormatter{
        get {
            // date shown as date in some tableviews
            formatter.dateFormat = "yyyy-MM-dd"
            return formatter
        }
    }

    var timeFormatter : NSDateFormatter{
        get {
            // date shown as time in some tableviews
            formatter.dateFormat = "HH:mm"
            return formatter
        }
    }

    class var sharedManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        return Static.instance
    }

    // MARK: - Helpers
    func dateStringFromDate(date: NSDate) -> String {
        return self.dateFormatter.stringFromDate(date)
    }
    func dateFromDateString(date: String) -> NSDate? {
        return self.dateFormatter.dateFromString(date)!
    }
    func timeStringFromDate(date: NSDate) -> String {
        return self.timeFormatter.stringFromDate(date)
    }
    func dateFromTimeString(date: String) -> NSDate? {
        return self.timeFormatter.dateFromString(date)!
    }
}

// Usage would be something like: 
var DateformatterManager.sharedManager.dateFromDateString("2014-12-05")

Would either of those be a good or a horrible idea? And is switching the format also expensive?

Update: As Hot Licks and Lorenzo Rossi point out, switching the formats is probably not such a good idea (Not thread safe and just as expensive as re-creating..).

Answer

Mobile Ben picture Mobile Ben · Dec 8, 2014

I'll chime in here with an answer based on experience. The answer is yes, caching NSDateFormatter app-wide is a good idea, however, for added safety there is a step you want to take for this.

Why is it good? Performance. It turns out that creating NSDateFormatters is actually slow. I worked on an app that was highly localized and used a lot of NSDateFormatters as well as NSNumberFormatters. There were times that we dynamically created them greedily within methods as well as having classes that had their own copy of the formatters they needed. In addition, we had the added burden that there were cases where we could also display strings localized for different locales on the same screen. We were noticing that our app was running slow in certain cases, and after running Instruments, we realized it was formatter creation. For example we saw performance hit when scrolling table views with a large number of cells. So we ended up caching them by creating a singleton object which vended the appropriate formatter.

A call would look something like:

NSDateFormatter *dateFormatter = [[FormatterVender sharedInstance] shortDate];

Note, this is Obj-C, but the equivalent can be made in Swift. It just happened ours was in Obj-C.

As of iOS 7, NSDateFormatters and NSNumberFormatters are "thread safe", however as Hot Licks mentioned, you probably don't want to go around modifying the format if another thread is utilizing it. Another +1 for caching them.

And another benefit I just thought of was code maintainability. Especially if you have a large team like we have. Because all devs know there is a centralized object that vends the formatters, they can simply see if the formatter they need already exists. If it doesn't, it gets added. This is typically feature related, and hence usually means that new formatter will be needed elsewhere as well. This also helps reduce bugs, because if there happens to be a bug in the formatter, you fix it one spot. But we usually catch that during the unit tests for the new formatter.

There is one more element you can add for safety, if you want. Which is you can use the NSThread's threadDictionary to store the formatter. In other words, when you call the singleton which will vend the formatter, that class checks the current thread's threadDictionary to see if that formatter exists or not. If it exists, then it simply returns it. If not, it creates it and then returns it. This adds a level of safety so if, for some reason you wanted to modify your formatter, you can do it and not have to worry about that the formatter is being modified by another thread.

What we used at the end of the day was singleton which vended specific formatters (both NSDateFormatter and NSNumberFormatter), ensuring that each thread itself had it's own copy of that specific formatter (note the app was created prior to iOS 7, which made that an essential thing to do). Doing that improved our app performance as well as got rid of some nasty side effects we experienced due to thread safety and formatters. Since we have the threadDictionary part in place, I never tested it to see if we had any issues on iOS7+ without it (ie. they have truly become thread-safe). Thus why I added the "if you want" above.