Skip to content

Commit

Permalink
[ios] Let RCTAsyncLocalStorage take storage directory as parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
nikki93 authored and James Ide committed May 30, 2017
1 parent f3ba9d7 commit 49bfd6c
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 77 deletions.
8 changes: 8 additions & 0 deletions RNTester/RNTester/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#import "AppDelegate.h"

#import <React/RCTAsyncLocalStorage.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTJavaScriptLoader.h>
Expand Down Expand Up @@ -109,4 +110,11 @@ - (void)application:(__unused UIApplication *)application didReceiveLocalNotific

#endif

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(__unused RCTBridge *)bridge
{
NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *storageDirectory = [documentDirectory stringByAppendingPathComponent:@"RCTAsyncLocalStorage_V1"];
return @[[[RCTAsyncLocalStorage alloc] initWithStorageDirectory:storageDirectory]];
}

@end
6 changes: 3 additions & 3 deletions React/Modules/RCTAsyncLocalStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

@property (nonatomic, readonly, getter=isValid) BOOL valid;

// NOTE(nikki): Added to allow scoped per Expo app
- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory;

// Clear the RCTAsyncLocalStorage data from native code
- (void)clearAllData;

// For clearing data when the bridge may not exist, e.g. when logging out.
+ (void)clearAllData;

@end
126 changes: 52 additions & 74 deletions React/Modules/RCTAsyncLocalStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,6 @@ static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> *
return nil;
}

static NSString *RCTGetStorageDirectory()
{
static NSString *storageDirectory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if TARGET_OS_TV
storageDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
#else
storageDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
#endif
storageDirectory = [storageDirectory stringByAppendingPathComponent:RCTStorageDirectory];
});
return storageDirectory;
}

static NSString *RCTGetManifestFilePath()
{
static NSString *manifestFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName];
});
return manifestFilePath;
}

// Only merges objects - all other types are just clobbered (including arrays)
static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
{
Expand Down Expand Up @@ -115,73 +90,78 @@ static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *so
return modified;
}

static dispatch_queue_t RCTGetMethodQueue()
{
// We want all instances to share the same queue since they will be reading/writing the same files.
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
// NOTE(nikki93): We replace with scoped implementations of:
// RCTGetStorageDirectory()
// RCTGetManifestFilePath()
// RCTGetMethodQueue()
// RCTGetCache()
// RCTDeleteStorageDirectory()

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;
}
#define RCTGetStorageDirectory() _storageDirectory
#define RCTGetManifestFilePath() _manifestFilePath
#define RCTGetMethodQueue() self.methodQueue
#define RCTGetCache() self.cache

static BOOL RCTHasCreatedStorageDirectory = NO;
static NSDictionary *RCTDeleteStorageDirectory()
static NSDictionary *RCTDeleteStorageDirectory(NSString *storageDirectory)
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error];
RCTHasCreatedStorageDirectory = NO;
[[NSFileManager defaultManager] removeItemAtPath:storageDirectory error:&error];
return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil;
}
#define RCTDeleteStorageDirectory() RCTDeleteStorageDirectory(_storageDirectory)

#pragma mark - RCTAsyncLocalStorage

@interface RCTAsyncLocalStorage ()

@property (nonatomic, copy) NSString *storageDirectory;
@property (nonatomic, copy) NSString *manifestFilePath;

@end

@implementation RCTAsyncLocalStorage
{
BOOL _haveSetup;
// 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<NSString *, NSString *> *_manifest;
NSCache *_cache;
dispatch_once_t _cacheOnceToken;
}

RCT_EXPORT_MODULE()

- (dispatch_queue_t)methodQueue
// NOTE(nikki93): Prevents the module from being auto-initialized and allows us to pass our own `storageDirectory`
+ (NSString *)moduleName { return @"RCTAsyncLocalStorage"; }
- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory
{
return RCTGetMethodQueue();
if ((self = [super init])) {
_storageDirectory = storageDirectory;
_manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName];
}
return self;
}

- (void)clearAllData
// NOTE(nikki93): Use the default `methodQueue` since instances have different storage directories
@synthesize methodQueue = _methodQueue;

- (NSCache *)cache
{
dispatch_async(RCTGetMethodQueue(), ^{
[self->_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
dispatch_once(&_cacheOnceToken, ^{
_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;
}

+ (void)clearAllData
- (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[self->_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
Expand Down Expand Up @@ -223,15 +203,13 @@ - (NSDictionary *)_ensureSetup
#endif

NSError *error = nil;
if (!RCTHasCreatedStorageDirectory) {
[[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory()
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
return RCTMakeError(@"Failed to create storage directory.", error, nil);
}
RCTHasCreatedStorageDirectory = YES;
// NOTE(nikki93): `withIntermediateDirectories:YES` makes this idempotent
[[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory()
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
return RCTMakeError(@"Failed to create storage directory.", error, nil);
}
if (!_haveSetup) {
NSDictionary *errorOut;
Expand Down

0 comments on commit 49bfd6c

Please sign in to comment.