Improved AsyncLocalStorage performance

Reviewed By: javache

Differential Revision: D2540626

fb-gh-sync-id: f94def7463075d40c2dcfea1cb4c01502aefa117
This commit is contained in:
Nick Lockwood 2015-10-20 09:40:24 -07:00 committed by facebook-github-bot-7
parent 4849aabecc
commit dbe37e7898

View File

@ -19,7 +19,7 @@
static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1"; static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
static NSString *const RCTManifestFileName = @"manifest.json"; static NSString *const RCTManifestFileName = @"manifest.json";
static const NSUInteger RCTInlineValueThreshold = 100; static const NSUInteger RCTInlineValueThreshold = 1024;
#pragma mark - Static helper functions #pragma mark - Static helper functions
@ -83,31 +83,32 @@ static NSString *RCTGetManifestFilePath()
} }
// Only merges objects - all other types are just clobbered (including arrays) // Only merges objects - all other types are just clobbered (including arrays)
static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source) // returns YES if destination was modified, or NO if no changes were needed.
static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
{ {
BOOL modified = NO;
for (NSString *key in source) { for (NSString *key in source) {
id sourceValue = source[key]; id sourceValue = source[key];
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
id destinationValue = destination[key]; id destinationValue = destination[key];
NSMutableDictionary *nestedDestination; if ([sourceValue isKindOfClass:[NSDictionary class]]) {
if ([destinationValue classForCoder] == [NSMutableDictionary class]) {
nestedDestination = destinationValue;
} else {
if ([destinationValue isKindOfClass:[NSDictionary class]]) { if ([destinationValue isKindOfClass:[NSDictionary class]]) {
// Ideally we wouldn't eagerly copy here... if ([destinationValue classForCoder] != [NSMutableDictionary class]) {
nestedDestination = [destinationValue mutableCopy]; destinationValue = [destinationValue mutableCopy];
}
if (RCTMergeRecursive(destinationValue, sourceValue)) {
destination[key] = destinationValue;
modified = YES;
}
} else { } else {
destination[key] = [sourceValue copy]; destination[key] = [sourceValue copy];
modified = YES;
}
} else if (![source isEqual:destinationValue]) {
destination[key] = [sourceValue copy];
modified = YES;
} }
} }
if (nestedDestination) { return modified;
RCTMergeRecursive(nestedDestination, sourceValue);
destination[key] = nestedDestination;
}
} else {
destination[key] = sourceValue;
}
}
} }
static dispatch_queue_t RCTGetMethodQueue() static dispatch_queue_t RCTGetMethodQueue()
@ -121,6 +122,23 @@ static dispatch_queue_t RCTGetMethodQueue()
return queue; return queue;
} }
static NSCache *RCTGetCache()
{
// We want all instances to share the same cache since they will be reading/writing the same files.
static NSCache *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [NSCache new];
cache.totalCostLimit = 2 * 1024 * 1024; // 2MB
// Clear cache in the event of a memory warning
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) {
[cache removeAllObjects];
}];
});
return cache;
}
static BOOL RCTHasCreatedStorageDirectory = NO; static BOOL RCTHasCreatedStorageDirectory = NO;
static NSError *RCTDeleteStorageDirectory() static NSError *RCTDeleteStorageDirectory()
{ {
@ -150,6 +168,7 @@ RCT_EXPORT_MODULE()
+ (void)clearAllData + (void)clearAllData
{ {
[RCTGetCache() removeAllObjects];
dispatch_async(RCTGetMethodQueue(), ^{ dispatch_async(RCTGetMethodQueue(), ^{
RCTDeleteStorageDirectory(); RCTDeleteStorageDirectory();
}); });
@ -158,6 +177,7 @@ RCT_EXPORT_MODULE()
- (void)invalidate - (void)invalidate
{ {
if (_clearOnInvalidate) { if (_clearOnInvalidate) {
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory(); RCTDeleteStorageDirectory();
} }
_clearOnInvalidate = NO; _clearOnInvalidate = NO;
@ -199,7 +219,7 @@ RCT_EXPORT_MODULE()
if (!_haveSetup) { if (!_haveSetup) {
NSDictionary *errorOut; NSDictionary *errorOut;
NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), nil, &errorOut); NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), nil, &errorOut);
_manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new]; _manifest = serialized ? RCTJSONParseMutable(serialized, &error) : [NSMutableDictionary new];
if (error) { if (error) {
RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error);
_manifest = [NSMutableDictionary new]; _manifest = [NSMutableDictionary new];
@ -222,23 +242,16 @@ RCT_EXPORT_MODULE()
return errorOut; return errorOut;
} }
- (id)_appendItemForKey:(NSString *)key toArray:(NSMutableArray *)result
{
id errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
id value = [self _getValueForKey:key errorOut:&errorOut];
[result addObject:@[key, RCTNullIfNil(value)]]; // Insert null if missing or failure.
return errorOut;
}
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut - (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
{ {
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value. NSString *value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
if (value == (id)kCFNull) { if (value == (id)kCFNull) {
value = [RCTGetCache() objectForKey:key];
if (!value) {
NSString *filePath = [self _filePathForKey:key]; NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, errorOut); value = RCTReadFile(filePath, key, errorOut);
[RCTGetCache() setObject:value forKey:key cost:value.length];
}
} }
return value; return value;
} }
@ -263,11 +276,13 @@ RCT_EXPORT_MODULE()
if (_manifest[key] && _manifest[key] != (id)kCFNull) { if (_manifest[key] && _manifest[key] != (id)kCFNull) {
// If the value already existed but wasn't inlined, remove the old file. // If the value already existed but wasn't inlined, remove the old file.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
} }
_manifest[key] = value; _manifest[key] = value;
return nil; return nil;
} }
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error]; [value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
[RCTGetCache() setObject:value forKey:key cost:value.length];
if (error) { if (error) {
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key}); errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
} else { } else {
@ -294,7 +309,9 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys
NSMutableArray *errors; NSMutableArray *errors;
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count]; NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSString *key in keys) { for (NSString *key in keys) {
id keyError = [self _appendItemForKey:key toArray:result]; id keyError;
id value = [self _getValueForKey:key errorOut:&keyError];
[result addObject:@[key, RCTNullIfNil(value)]];
RCTAppendError(keyError, &errors); RCTAppendError(keyError, &errors);
} }
callback(@[RCTNullIfNil(errors), result]); callback(@[RCTNullIfNil(errors), result]);
@ -331,20 +348,19 @@ RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs
for (__strong NSArray *entry in kvPairs) { for (__strong NSArray *entry in kvPairs) {
id keyError; id keyError;
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError]; NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
if (keyError) {
RCTAppendError(keyError, &errors);
} else {
if (value) {
NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy];
RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError));
entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)];
}
if (!keyError) { if (!keyError) {
if (value) {
NSMutableDictionary *mergedVal = RCTJSONParseMutable(value, &keyError);
if (RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError))) {
entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)];
keyError = [self _writeEntry:entry]; keyError = [self _writeEntry:entry];
} }
RCTAppendError(keyError, &errors); } else {
keyError = [self _writeEntry:entry];
} }
} }
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors]; [self _writeManifest:&errors];
if (callback) { if (callback) {
callback(@[RCTNullIfNil(errors)]); callback(@[RCTNullIfNil(errors)]);
@ -363,8 +379,10 @@ RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
for (NSString *key in keys) { for (NSString *key in keys) {
id keyError = RCTErrorForKey(key); id keyError = RCTErrorForKey(key);
if (!keyError) { if (!keyError) {
if ( _manifest[key] == (id)kCFNull) {
NSString *filePath = [self _filePathForKey:key]; NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
[_manifest removeObjectForKey:key]; [_manifest removeObjectForKey:key];
} }
RCTAppendError(keyError, &errors); RCTAppendError(keyError, &errors);
@ -377,7 +395,8 @@ RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
{ {
_manifest = [NSMutableDictionary new]; [_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
NSError *error = RCTDeleteStorageDirectory(); NSError *error = RCTDeleteStorageDirectory();
if (callback) { if (callback) {
callback(@[RCTNullIfNil(error)]); callback(@[RCTNullIfNil(error)]);