Ben Copsey

S3 - use per-thread NSDateFormatter. Many thanks once again to Tom Andersen!

Add same to Cloud Files
... ... @@ -23,7 +23,7 @@
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.6.2-10 2010-05-14";
NSString *ASIHTTPRequestVersion = @"v1.6.2-11 2010-05-26";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ...
... ... @@ -103,15 +103,21 @@ static NSRecursiveLock *accessDetailsLock = nil;
#pragma mark -
#pragma mark Date Parser
-(NSDate *)dateFromString:(NSString *)dateString {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
// example: 2009-11-04T19:46:20.192723
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'H:mm:ss.SSSSSS"];
NSDate *date = [dateFormatter dateFromString:dateString];
[dateFormatter release];
return date;
-(NSDate *)dateFromString:(NSString *)dateString
{
// We store our date formatter in the calling thread's dictionary
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread
// This formatter can be reused many times in parsing a single response, so it would be expensive to keep creating new date formatters
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASICloudFilesResponseDateFormatter"];
if (dateFormatter == nil) {
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
// example: 2009-11-04T19:46:20.192723
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'H:mm:ss.SSSSSS"];
[threadDict setObject:dateFormatter forKey:@"ASICloudFilesResponseDateFormatter"];
}
return [dateFormatter dateFromString:dateString];
}
@end
... ...
... ... @@ -127,7 +127,7 @@
} else if ([elementName isEqualToString:@"Key"]) {
[[self currentObject] setKey:[self currentXMLElementContent]];
} else if ([elementName isEqualToString:@"LastModified"]) {
[[self currentObject] setLastModified:[[ASIS3Request dateFormatter] dateFromString:[self currentXMLElementContent]]];
[[self currentObject] setLastModified:[[ASIS3Request S3ResponseDateFormatter] dateFromString:[self currentXMLElementContent]]];
} else if ([elementName isEqualToString:@"ETag"]) {
[[self currentObject] setETag:[self currentXMLElementContent]];
} else if ([elementName isEqualToString:@"Size"]) {
... ...
... ... @@ -71,7 +71,11 @@ typedef enum _ASIS3ErrorType {
# pragma mark helpers
// Returns a date formatter than can be used to parse a date from S3
+ (NSDateFormatter *)dateFormatter;
+ (NSDateFormatter*)S3ResponseDateFormatter;
// Returns a date formatter than can be used to send a date header to S3
+ (NSDateFormatter*)S3RequestDateFormatter;
// URL-encodes an S3 key so it can be used in a url
// You shouldn't normally need to use this yourself
... ...
... ... @@ -17,8 +17,6 @@ NSString* const ASIS3AccessPolicyAuthenticatedRead = @"authenticated-read";
static NSString *sharedAccessKey = nil;
static NSString *sharedSecretAccessKey = nil;
static NSDateFormatter *dateFormatter = nil;
// Private stuff
@interface ASIS3Request ()
+ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
... ... @@ -49,11 +47,7 @@ static NSDateFormatter *dateFormatter = nil;
- (void)setDate:(NSDate *)date
{
NSDateFormatter *headerDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
// Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/)
[headerDateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
[headerDateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
[self setDateString:[headerDateFormatter stringFromDate:date]];
[self setDateString:[[ASIS3Request S3RequestDateFormatter] stringFromDate:date]];
}
- (ASIHTTPRequest *)HEADRequest
... ... @@ -229,17 +223,39 @@ static NSDateFormatter *dateFormatter = nil;
return path;
}
+ (NSDateFormatter *)dateFormatter
// Thanks to Tom Andersen for pointing out the threading issues and providing this code!
+ (NSDateFormatter*)S3ResponseDateFormatter
{
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
// We store our date formatter in the calling thread's dictionary
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread
// This formatter can be reused 1000 times in parsing a single response, so it would be expensive to keep creating new date formatters
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIS3ResponseDateFormatter"];
if (dateFormatter == nil) {
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"];
[threadDict setObject:dateFormatter forKey:@"ASIS3ResponseDateFormatter"];
}
return dateFormatter;
}
+ (NSDateFormatter*)S3RequestDateFormatter
{
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIS3RequestHeaderDateFormatter"];
if (dateFormatter == nil) {
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
// Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/)
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
[threadDict setObject:dateFormatter forKey:@"ASIS3RequestHeaderDateFormatter"];
}
return dateFormatter;
}
// From: http://stackoverflow.com/questions/476455/is-there-a-library-for-iphone-to-work-with-hmac-sha-1-encoding
... ...
... ... @@ -58,7 +58,7 @@
} else if ([elementName isEqualToString:@"Name"]) {
[[self currentBucket] setName:[self currentXMLElementContent]];
} else if ([elementName isEqualToString:@"CreationDate"]) {
[[self currentBucket] setCreationDate:[[ASIS3Request dateFormatter] dateFromString:[self currentXMLElementContent]]];
[[self currentBucket] setCreationDate:[[ASIS3Request S3ResponseDateFormatter] dateFromString:[self currentXMLElementContent]]];
} else if ([elementName isEqualToString:@"ID"]) {
[self setOwnerID:[self currentXMLElementContent]];
} else if ([elementName isEqualToString:@"DisplayName"]) {
... ...