Cache NSDateFormatter

Alguns dias atrás, eu estava brincando com uma API JSON. A resposta desta API é uma longa lista de itens onde cada item é composto por um monte de campos e um carimbo de data / hora. Na classe de modelo para este item, desejo expor o carimbo de data / hora como propriedade NSDate, para fazer isso, devo convertê-lo da representação NSString para NSDate com a ajuda de NSDateFormatter. O código funciona bem, mas quando testei em um dispositivo real (iPhone 5) levou alguns segundos para executar a tarefa (eu percebi isso facilmente porque estava fazendo meu experimento no thread principal), com a ajuda de Instrument I facilmente descobrir que o problema estava relacionado a NSDateFormatter.

[NSDateFormatter init] é uma operação bastante cara. Se você tiver que fazer isso muitas vezes, como no meu caso, quando você analisa muitos itens, pode ser um problema de desempenho. Uma boa ideia é armazenar em cache uma única instância e reutilizá-la conforme proposto pela Apple em Cache Formatters for Efficiency, onde eles sugerem o uso de uma variável estática para manter a instância compartilhada, como você faria no caso de um singleton.

Esta solução é simples e funciona muito bem, exceto no caso de multithreading. NSDateFormatter não é thread-safe e citando Apple: “você não deve modificar um determinado formatador de data simultaneamente de vários threads”

Por esta razão, coisas realmente ruins podem acontecer com você se você tentar acessá-lo de vários tópicos. Uma solução melhor é a proposta em Threadsafe Date Formatting com o uso de Thread-Local Storage .

Aqui está o código:

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;
}

Uma pegadinha desse código é que o método retornará sempre a mesma instância se chamado no mesmo thread, se for chamado em um thread novo e diferente, uma nova instância é criada e retornada.