Skip to content

Commit

Permalink
Simplify code by using NSCache for the time zone cache dictionary ins…
Browse files Browse the repository at this point in the history
…tead of a slower thread local variant. Also removes the requirement of registering for memory warnings as NSCache cleans up automatically.
  • Loading branch information
steipete committed Mar 26, 2014
1 parent fed060f commit 98bf9cb
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 80 deletions.
78 changes: 8 additions & 70 deletions ISO8601DateFormatter.m
Expand Up @@ -25,12 +25,6 @@
#define ISO_TIMEZONE_OFFSET_FORMAT_NO_SEPARATOR @"%+.2d%.2d"
#define ISO_TIMEZONE_OFFSET_FORMAT_WITH_SEPARATOR @"%+.2d%C%.2d"

@interface ISO8601DateFormatter ()
+ (void) createGlobalCachesThatDoNotAlreadyExist;
//Used when a memory warning occurs (if at least one ISO 8601 Date Formatter exists at the time).
+ (void) purgeGlobalCaches;
@end

@interface ISO8601DateFormatter(UnparsingPrivate)

- (NSString *) replaceColonsInString:(NSString *)timeFormat withTimeSeparator:(unichar)timeSep;
Expand All @@ -40,40 +34,16 @@ - (NSString *) weekDateStringForDate:(NSDate *)date timeZone:(NSTimeZone *)timeZ

@end

@interface ISO8601TimeZoneCache: NSObject
{}

//The property being read-only means that the formatter cannot change the cache's dictionary, but the formatter is explicitly allowed to mutate the dictionary.
@property(nonatomic, readonly, strong) NSMutableDictionary *timeZonesByOffset;

@end

static ISO8601TimeZoneCache *timeZoneCache;

#if ISO8601_TESTING_PURPOSES_ONLY
//This method only exists for use by the project's test cases. DO NOT use this in an application.
extern bool ISO8601DateFormatter_GlobalCachesAreWarm(void);

bool ISO8601DateFormatter_GlobalCachesAreWarm(void) {
return (timeZoneCache != nil) && (timeZoneCache.timeZonesByOffset.count > 0);
}
#endif
static NSCache *timeZonesByOffset;

@implementation ISO8601DateFormatter
+ (void) initialize {
[self createGlobalCachesThatDoNotAlreadyExist];
}

+ (void) createGlobalCachesThatDoNotAlreadyExist {
if (!timeZoneCache) {
timeZoneCache = [[ISO8601TimeZoneCache alloc] init];
}
+ (void) initialize {
timeZonesByOffset = [[NSCache alloc] init];
}

+ (void) purgeGlobalCaches {
ISO8601TimeZoneCache *oldCache = timeZoneCache;
timeZoneCache = nil;
[oldCache release];
[timeZonesByOffset removeAllObjects];
}

- (NSCalendar *) makeCalendarWithDesiredConfiguration {
Expand All @@ -92,22 +62,11 @@ - (id) init {
timeSeparator = ISO8601DefaultTimeSeparatorCharacter;
includeTime = NO;
parsesStrictly = NO;

#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
return self;
}

- (void) dealloc {
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif

[defaultTimeZone release];

[unparsingFormatter release];
Expand All @@ -118,10 +77,6 @@ - (void) dealloc {
[super dealloc];
}

- (void) didReceiveMemoryWarning:(NSNotification *)notification {
[[self class] purgeGlobalCaches];
}

@synthesize defaultTimeZone;
- (void) setDefaultTimeZone:(NSTimeZone *)tz {
if (defaultTimeZone != tz) {
Expand Down Expand Up @@ -607,15 +562,14 @@ - (NSDateComponents *) dateComponentsFromString:(NSString *)string timeZone:(out
if (negative) tz_minute = -tz_minute;
}

[[self class] createGlobalCachesThatDoNotAlreadyExist];

NSInteger timeZoneOffset = (tz_hour * 3600) + (tz_minute * 60);
NSNumber *offsetNum = [NSNumber numberWithInteger:timeZoneOffset];
timeZone = [timeZoneCache.timeZonesByOffset objectForKey:offsetNum];
timeZone = [timeZonesByOffset objectForKey:offsetNum];
if (!timeZone) {
timeZone = [NSTimeZone timeZoneForSecondsFromGMT:timeZoneOffset];
if (timeZone)
[timeZoneCache.timeZonesByOffset setObject:timeZone forKey:offsetNum];
if (timeZone) {
[timeZonesByOffset setObject:timeZone forKey:offsetNum];
}
}
}
}
Expand Down Expand Up @@ -1002,19 +956,3 @@ static BOOL is_leap_year(NSUInteger year) {
&& (((year % 100U) != 0U)
|| ((year % 400U) == 0U));
}

static NSString *const ISO8601ThreadStorageTimeZoneCacheKey = @"org.boredzo.ISO8601ThreadStorageTimeZoneCacheKey";

@implementation ISO8601TimeZoneCache: NSObject

- (NSMutableDictionary *) timeZonesByOffset {
NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary;
NSMutableDictionary *currentCacheDict = [threadDict objectForKey:ISO8601ThreadStorageTimeZoneCacheKey];
if (currentCacheDict == nil) {
currentCacheDict = [NSMutableDictionary dictionaryWithCapacity:2UL];
[threadDict setObject:currentCacheDict forKey:ISO8601ThreadStorageTimeZoneCacheKey];
}
return currentCacheDict;
}

@end
Expand Up @@ -10,8 +10,6 @@

#import "ISO8601DateFormatter.h"

extern bool ISO8601DateFormatter_GlobalCachesAreWarm(void);

@interface ISO8601DateFormatter (ISO8601MemoryWarningTesting)
+ (void) purgeGlobalCaches;
@end
Expand Down Expand Up @@ -39,8 +37,6 @@ - (void) tearDown {
}

- (void) testMemoryWarning {
STAssertFalseNoThrow(ISO8601DateFormatter_GlobalCachesAreWarm(), @"Global caches are already warm before using an ISO 8601 date formatter!");

//Now parse a bunch of dates to try to warm the caches.
[_iso8601DateFormatter dateFromString:@"2013-09-18T07:34:21-1200"];
[_iso8601DateFormatter dateFromString:@"2013-09-18T07:34:21-0800"];
Expand All @@ -50,12 +46,6 @@ - (void) testMemoryWarning {
[_iso8601DateFormatter dateFromString:@"2013-09-18T07:34:21+0130"];
[_iso8601DateFormatter dateFromString:@"2013-09-18T07:34:21+0800"];
[_iso8601DateFormatter dateFromString:@"2013-09-18T07:34:21+1200"];

STAssertTrueNoThrow(ISO8601DateFormatter_GlobalCachesAreWarm(), @"Global caches were not warmed by using an ISO 8601 date formatter!");

[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification object:[UIApplication sharedApplication]];

STAssertFalseNoThrow(ISO8601DateFormatter_GlobalCachesAreWarm(), @"Global caches were not purged by a (simulated) memory warning!");
}

@end

0 comments on commit 98bf9cb

Please sign in to comment.