mirror of
https://github.com/status-im/react-native.git
synced 2025-02-27 08:30:34 +00:00
Improved AsyncLocalStorage performance
Reviewed By: javache Differential Revision: D2540626 fb-gh-sync-id: f94def7463075d40c2dcfea1cb4c01502aefa117
This commit is contained in:
parent
4849aabecc
commit
dbe37e7898
@ -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)]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user