Creating shared instances of non thread-safe classes
Often, when coding, you find yourself allocating the very same object again and again. A common use in order to avoid wasting resources is to store a static reference to a single instance of this class and keep returning it when needed.
A classic example is NSDateFormatter
. Assume you have a UITableView
displaying a date in each UITableViewCell
in a particular format. You might have the date stored in a database but you will need an instance of NSDateFormatter
to be able to transform it into a string the way you intend. Obviously, creating an instance of NSDateFormatter
with the same NSLocale
and format for each cell of your UITableView
is wasting resources and memory.
This is the reason why many programmers use the following sort of method
+ (NSDateFormatter *)dateFormatter
{
static NSDateFormatter *dateFormatter = nil ;
if (dateFormatter == nil)
{
dateFormatter = [[NSDateFormatter alloc] init] ;
[dateFormatter setLocale: [[[NSLocale alloc] initWithLocaleIdentifier: @"en_GB"] autorelease]] ;
[dateFormatter setDateFormat: @"YYYY-MM-dd HH:mm:ss ZZZ"] ;
}
return dateFormatter ;
}
This way, whenever you need an instance of NSDateFormatter
, call
NSDateFormatter *dateFormatter = [[self class] dateFormatter] ;
to get a reference to the shared NSDateFormatter
.
The problem with this method is that NSDateFormatter
is not thread-safe. This means that if you happen to call this method from different threads, you might get unexpected behaviors.
Obviously, you are not left with creating single instances for every time you need one, there is an easy way to create an instance for every thread you call this method from. This way, you won't have to create as many objects as the number of times you call the method but simply as many objects as the number of different threads your are calling the method from.
To achieve this, we use a cool feature in NSThread
:
- (NSMutableDictionary *)threadDictionary
The thread dictionary is a classic dictionary where you can store thread-specific data. Any thread has got its own dictionary and anyone is free to store any interesting data.
We can thus rewrite our shared NSDateFormatter
as following:
+ (NSDateFormatter *)currentDateFormatter
{
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary] ;
NSDateFormatter *dateFormatter = [threadDictionary objectForKey: @"DDMyDateFormatter"] ;
if (dateFormatter == nil)
{
dateFormatter = [[NSDateFormatter alloc] init] ;
[dateFormatter setLocale: [[[NSLocale alloc] initWithLocaleIdentifier: @"en_GB"] autorelease]] ;
[dateFormatter setDateFormat: @"YYYY-MM-dd HH:mm:ss ZZZ"] ;
[threadDictionary setObject: dateFormatter forKey: @"DDMyDateFormatter"] ;
}
return dateFormatter ;
}
Let's go through this method. We first get a reference to the current thread dictionary. We then check whether we previously store an instance of NSDateFormatter
with a specific key. In case we did, we simply return it. If it is the first time we access this method from this thread, the dictionary won't have any NSDateFormatter
object stored for the key and we will simply create one and store it in the dictionary before returning it.
Easy!
PS: For the same reason, I also have to implement this method for NSCalendar
.