Is caching a NSDateformatter application-wide good idea?

10.1k views Asked by At

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..).

8

There are 8 answers

6
Mobile Ben On BEST ANSWER

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.

4
Gregory Furmanek On

In Objc:

Considering NSDateFormatter is considered thread-unsafe using a single formatter in your application may not be the best idea. Having one formatter per thread may be a way to go. Alternatively you could consider wrapping the formatter in a thread safe class.

From Apple Docs: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

In Swift:

If swift offers thread safety of the class than there should be no problem having one instance.

0
Michael Voznesensky On

Instead of using a singleton, use dependency injection. Remember to follow the 0, 1, infinite rule.

http://en.wikipedia.org/wiki/Zero_one_infinity_rule

Here, we clearly cant have 0, and while 1 sounds nice, if you are going to use it from multiple thread, then you can't have just one without it hanging. So, infinity.

A good way to approach this is just to be careful with how many you spawn, keep just one around for each thread you open up, and be sure that they get cleaned up as soon as you are done using them.

To help you, check out this other stackoverflow link - I am afraid my answer will be in line with theirs (keep the number of NSDateformatters down to a minimum). However, they may have some reasoning that was not covered in this thread (no pun inteded!)

How to minimize the costs for allocating and initializing an NSDateFormatter?

Also, if I may ask - if you are are faced with this question, perhaps there is somewhere in your program where a flow can be improved to avoid even needing so many formatters?

2
Midhun MP On

In my opinion Caching NSDateFormatter is a good idea if your app is using that widely or through-out your app, it'll increase the performance of your app. If you need that in 1 or 2 places, it won't be a good idea. However changing the date format is not a good idea, it can lead you into undesired situations. (You need to keep track the current format each time before going to use it)

In one of my application I used a singleton with three date format objects(all three contains three different formats) as properties. And custom getters for each NSDateFormatter

+ (instancetype)defaultDateManager
{
    static DateManager *dateManager = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        dateManager                = [[DateManager alloc] init];
    });

    return dateManager;
}

// Custom Getter for short date
- (NSDateFormatter *)shortDate
{
    if (!_shortDateFormatter)
    {
        _shortDateFormatter = [[NSDateFormatter alloc] init];
        [_shortDateFormatter setDateFormat:@"yyyy-MM-dd"]
    }
    return _shortDateFormatter
}

Like this I implemented custom getters for the other two also.

Why I implemented custom getters ? Why I didn't allocate the NSDateFormatter during singleton initialization ?

It's because I don't want to allocate them in the beginning itself. I need to allocate it when it's needed for the first time (On a On Demand basis). In my app the all three NSDateFormatters are not widely used, that's why I chose such a pattern for implementing it. (My app is in Objective C, that's why I used the Objective C code here)

0
justin On

Would either of those be a good or a horrible idea?

Introducing singletons (anytime you need a new variant of a formatter) is not a good solution. Creating formatters isn't that expensive.

Instead, devise ways to reuse and share instances of formatters and pass them around in your program, like regular variables. This approach is quite simple to introduce into an existing program.

Instruments will help you identify where your program creates many formatters, and you can consider how to reuse formatters based on that data.

And is switching the format also expensive?

Don't bother mutating formatters you share, unless used in very specific/local contexts (such as a particular collection of views). This is going to be much easier to achieve expected results. Instead, copy then mutate if you need a variant of a shared formatter.

8
Tim Bernikovich On

Since Swift uses dispatch once methods for static properties creation it's really fast and safe to create DateFormatter in a such way.

extension DateFormatter {
    static let shortFormatDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

Than just write

date = DateFormatter.shortFormatDateFormatter.string(from: json["date"])
0
user3378170 On

Swift Example

Based on @Mobile Ben answer: this is an example of a simple Swift singleton.

class YourDateFormatter {

    // MARK: - Properties

    static let sharedFormatter = YourDateFormatter()
    /// only date format
    private let dateFormatter: NSDateFormatter
    /// only time format
    private let timeFormatter: NSDateFormatter

    // MARK: - private init

    private init() {

        // init the formatters

        dateFormatter = NSDateFormatter()
        timeFormatter = NSDateFormatter()

        // config the format

        dateFormatter.dateStyle = .MediumStyle
        dateFormatter.timeStyle = .NoStyle
        dateFormatter.doesRelativeDateFormatting = true

        timeFormatter.dateStyle = .NoStyle
        timeFormatter.timeStyle = .MediumStyle

    }

    // MARK: - Public 

    func dateFormat(date: NSDate) -> String {
        return dateFormatter.stringFromDate(date)
    }

    func timeFormat(date: NSDate) -> String {
        return timeFormatter.stringFromDate(date)
    }

    func dateTimeFormat(date: NSDate) -> String {
        let dateFormat = self.dateFormat(date)
        let timeFormat = self.timeFormat(date)

        return dateFormat + " - " + timeFormat
    }
}
3
the_critic On

I'd also like to expand on Mobile Ben's answer by providing an example:

import Foundation

public class DateFormatter : NSDateFormatter{

  public class func sharedFormatter() -> NSDateFormatter {
    // current thread's hash
    let threadHash = NSThread.currentThread().hash
    // check if a date formatter has already been created for this thread
    if let existingFormatter = NSThread.currentThread().threadDictionary[threadHash] as? NSDateFormatter{
      // a date formatter has already been created, return that
      return existingFormatter
    }else{
      // otherwise, create a new date formatter 
      let dateFormatter = NSDateFormatter()
      // and store it in the threadDictionary (so that we can access it later on in the current thread)
      NSThread.currentThread().threadDictionary[threadHash] = dateFormatter
      return dateFormatter

    }

  }

}

This is used in a library, that's why you can see the public modifier throughout.