Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added in-memory cache for AsyncLocalStorage
Reviewed By: tadeuzagallo

Differential Revision: D2641705

fb-gh-sync-id: 40b96b3084b82779e16f8845f9faeb0e3638d189
  • Loading branch information
nicklockwood authored and facebook-github-bot-4 committed Nov 14, 2015
1 parent 90c8f37 commit 397791f
Showing 1 changed file with 76 additions and 24 deletions.
100 changes: 76 additions & 24 deletions React/Modules/RCTAsyncLocalStorage.m
Expand Up @@ -122,6 +122,23 @@ static dispatch_queue_t RCTGetMethodQueue()
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 NSDictionary *RCTDeleteStorageDirectory()
{
Expand All @@ -139,7 +156,7 @@ @implementation RCTAsyncLocalStorage
// The manifest is a dictionary of all keys with small values inlined. Null values indicate values that are stored
// in separate files (as opposed to nil values which don't exist). The manifest is read off disk at startup, and
// written to disk after all mutations.
NSMutableDictionary *_manifest;
NSMutableDictionary<NSString *, NSString *> *_manifest;
}

RCT_EXPORT_MODULE()
Expand All @@ -152,25 +169,28 @@ - (dispatch_queue_t)methodQueue
- (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
_manifest = [NSMutableDictionary new];
[_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}

+ (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}

- (void)invalidate
{
if (_clearOnInvalidate) {
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
}
_clearOnInvalidate = NO;
_manifest = [NSMutableDictionary new];
[_manifest removeAllObjects];
_haveSetup = NO;
}

Expand Down Expand Up @@ -245,15 +265,25 @@ - (NSDictionary *)_appendItemForKey:(NSString *)key

- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
{
id value = _manifest[key]; // nil means missing, null means there is a data file, else: NSString
NSString *value = _manifest[key]; // nil means missing, null means there may be a data file, else: NSString
if (value == (id)kCFNull) {
NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, errorOut);
value = [RCTGetCache() objectForKey:key];
if (!value) {
NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, errorOut);
if (value) {
[RCTGetCache() setObject:value forKey:key cost:value.length];
} else {
// file does not exist after all, so remove from manifest (no need to save
// manifest immediately though, as cost of checking again next time is negligible)
[_manifest removeObjectForKey:key];
}
}
}
return value;
}

- (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
- (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL *)changedManifest
{
if (entry.count != 2) {
return RCTMakeAndLogError(@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
Expand All @@ -267,18 +297,22 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
NSString *filePath = [self _filePathForKey:key];
NSError *error;
if (value.length <= RCTInlineValueThreshold) {
if (_manifest[key] && _manifest[key] != (id)kCFNull) {
if (_manifest[key] == (id)kCFNull) {
// If the value already existed but wasn't inlined, remove the old file.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
}
*changedManifest = YES;
_manifest[key] = value;
return nil;
}
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
[RCTGetCache() setObject:value forKey:key cost:value.length];
if (error) {
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
} else {
_manifest[key] = (id)kCFNull; // Mark existence of file with null, any other value is inline data.
} else if (_manifest[key] != (id)kCFNull) {
*changedManifest = YES;
_manifest[key] = (id)kCFNull;
}
return errorOut;
}
Expand All @@ -296,7 +330,9 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
NSMutableArray<NSDictionary *> *errors;
NSMutableArray<NSArray<NSString *> *> *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSString *key in keys) {
NSDictionary *keyError = [self _appendItemForKey:key toArray:result];
id keyError;
id value = [self _getValueForKey:key errorOut:&keyError];
[result addObject:@[key, RCTNullIfNil(value)]];
RCTAppendError(keyError, &errors);
}
callback(@[RCTNullIfNil(errors), result]);
Expand All @@ -310,12 +346,15 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError = [self _writeEntry:entry];
NSDictionary *keyError = [self _writeEntry:entry changedManifest:&changedManifest];
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors];
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}

Expand All @@ -327,13 +366,12 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (__strong NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError;
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
if (keyError) {
RCTAppendError(keyError, &errors);
} else {
if (!keyError) {
if (value) {
NSError *jsonError;
NSMutableDictionary *mergedVal = RCTJSONParseMutable(value, &jsonError);
Expand All @@ -345,12 +383,14 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
}
}
if (!keyError) {
keyError = [self _writeEntry:entry];
keyError = [self _writeEntry:entry changedManifest:&changedManifest];
}
RCTAppendError(keyError, &errors);
}
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
[self _writeManifest:&errors];
callback(@[RCTNullIfNil(errors)]);
}

Expand All @@ -363,22 +403,34 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry
return;
}
NSMutableArray<NSDictionary *> *errors;
BOOL changedManifest = NO;
for (NSString *key in keys) {
NSDictionary *keyError = RCTErrorForKey(key);
if (!keyError) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[_manifest removeObjectForKey:key];
if (_manifest[key] == (id)kCFNull) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
// remove the key from manifest, but no need to mark as changed just for
// this, as the cost of checking again next time is negligible.
[_manifest removeObjectForKey:key];
} else if (_manifest[key]) {
changedManifest = YES;
[_manifest removeObjectForKey:key];
}
}
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors];
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}

RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
{
_manifest = [NSMutableDictionary new];
[_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
NSDictionary *error = RCTDeleteStorageDirectory();
callback(@[RCTNullIfNil(error)]);
}
Expand Down

0 comments on commit 397791f

Please sign in to comment.