1--- ios/RNCAsyncStorage.h 2+++ ios/RNCAsyncStorage.h 3@@ -31,12 +31,12 @@ 4 5 @property (nonatomic, readonly, getter=isValid) BOOL valid; 6 7+// NOTE(nikki): Added to allow scoped per Expo app 8+- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory; 9+ 10 // Clear the RNCAsyncStorage data from native code 11 - (void)clearAllData; 12 13-// For clearing data when the bridge may not exist, e.g. when logging out. 14-+ (void)clearAllData; 15- 16 // Grab data from the cache. ResponseBlock result array will have an error at position 0, and an 17 // array of arrays at position 1. 18 - (void)multiGet:(NSArray<NSString *> *)keys callback:(RCTResponseSenderBlock)callback; 19--- ios/RNCAsyncStorage.m 20+++ ios/RNCAsyncStorage.m 21@@ -14,7 +14,9 @@ 22 #import <React/RCTLog.h> 23 #import <React/RCTUtils.h> 24 25-static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1"; 26+// NOTE(kudo): Use Expo storage directory for backward compatibility 27+//static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1"; 28+static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage"; 29 static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1"; 30 static NSString *const RCTExpoStorageDirectory = @"RCTAsyncLocalStorage"; 31 static NSString *const RCTManifestFileName = @"manifest.json"; 32@@ -150,31 +152,11 @@ static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir) 33 return storageDirectoryPath; 34 } 35 36-static NSString *RCTGetStorageDirectory() 37-{ 38- static NSString *storageDirectory = nil; 39- static dispatch_once_t onceToken; 40- dispatch_once(&onceToken, ^{ 41- storageDirectory = RCTCreateStorageDirectoryPath(RCTStorageDirectory); 42- }); 43- return storageDirectory; 44-} 45- 46 static NSString *RCTCreateManifestFilePath(NSString *storageDirectory) 47 { 48 return [storageDirectory stringByAppendingPathComponent:RCTManifestFileName]; 49 } 50 51-static NSString *RCTGetManifestFilePath() 52-{ 53- static NSString *manifestFilePath = nil; 54- static dispatch_once_t onceToken; 55- dispatch_once(&onceToken, ^{ 56- manifestFilePath = RCTCreateManifestFilePath(RCTStorageDirectory); 57- }); 58- return manifestFilePath; 59-} 60- 61 // Only merges objects - all other types are just clobbered (including arrays) 62 static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source) 63 { 64@@ -203,51 +185,27 @@ static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *so 65 return modified; 66 } 67 68-static dispatch_queue_t RCTGetMethodQueue() 69-{ 70- // We want all instances to share the same queue since they will be reading/writing the same 71- // files. 72- static dispatch_queue_t queue; 73- static dispatch_once_t onceToken; 74- dispatch_once(&onceToken, ^{ 75- queue = 76- dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); 77- }); 78- return queue; 79-} 80- 81-static NSCache *RCTGetCache() 82-{ 83- // We want all instances to share the same cache since they will be reading/writing the same 84- // files. 85- static NSCache *cache; 86- static dispatch_once_t onceToken; 87- dispatch_once(&onceToken, ^{ 88- cache = [NSCache new]; 89- cache.totalCostLimit = 2 * 1024 * 1024; // 2MB 90- 91-#if !TARGET_OS_OSX 92- // Clear cache in the event of a memory warning 93- [[NSNotificationCenter defaultCenter] 94- addObserverForName:UIApplicationDidReceiveMemoryWarningNotification 95- object:nil 96- queue:nil 97- usingBlock:^(__unused NSNotification *note) { 98- [cache removeAllObjects]; 99- }]; 100-#endif // !TARGET_OS_OSX 101- }); 102- return cache; 103-} 104- 105 static BOOL RCTHasCreatedStorageDirectory = NO; 106-static NSDictionary *RCTDeleteStorageDirectory() 107+ 108+// NOTE(nikki93): We replace with scoped implementations of: 109+// RCTGetStorageDirectory() 110+// RCTGetManifestFilePath() 111+// RCTGetMethodQueue() 112+// RCTGetCache() 113+// RCTDeleteStorageDirectory() 114+ 115+#define RCTGetStorageDirectory() _storageDirectory 116+#define RCTGetManifestFilePath() _manifestFilePath 117+#define RCTGetMethodQueue() self.methodQueue 118+#define RCTGetCache() self.cache 119+ 120+static NSDictionary *RCTDeleteStorageDirectory(NSString *storageDirectory) 121 { 122- NSError *error; 123- [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error]; 124- RCTHasCreatedStorageDirectory = NO; 125- return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil; 126+ NSError *error; 127+ [[NSFileManager defaultManager] removeItemAtPath:storageDirectory error:&error]; 128+ return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil; 129 } 130+#define RCTDeleteStorageDirectory() RCTDeleteStorageDirectory(_storageDirectory) 131 132 static NSDate *RCTManifestModificationDate(NSString *manifestFilePath) 133 { 134@@ -285,35 +243,7 @@ static void RCTStorageDirectoryMigrate(NSString *oldDirectoryPath, 135 NSString *newDirectoryPath, 136 BOOL shouldCleanupOldDirectory) 137 { 138- NSError *error; 139- // Migrate data by copying old storage directory to new storage directory location 140- if (![[NSFileManager defaultManager] copyItemAtPath:oldDirectoryPath 141- toPath:newDirectoryPath 142- error:&error]) { 143- // the new storage directory "Application Support/[bundleID]/RCTAsyncLocalStorage_V1" seems 144- // unable to migrate because folder "Application Support/[bundleID]" doesn't exist.. create 145- // this folder and attempt folder copying again 146- if (error != nil && error.code == 4 && 147- [newDirectoryPath isEqualToString:RCTGetStorageDirectory()]) { 148- NSError *error = nil; 149- _createStorageDirectory(RCTCreateStorageDirectoryPath(@""), &error); 150- if (error == nil) { 151- RCTStorageDirectoryMigrate( 152- oldDirectoryPath, newDirectoryPath, shouldCleanupOldDirectory); 153- } else { 154- RCTStorageDirectoryMigrationLogError( 155- @"Failed to create storage directory during migration.", error); 156- } 157- } else { 158- RCTStorageDirectoryMigrationLogError( 159- @"Failed to copy old storage directory to new storage directory location during " 160- @"migration", 161- error); 162- } 163- } else if (shouldCleanupOldDirectory) { 164- // If copying succeeds, remove old storage directory 165- RCTStorageDirectoryCleanupOld(oldDirectoryPath); 166- } 167+ assert(false); 168 } 169 170 /** 171@@ -406,12 +336,49 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory, 172 173 #pragma mark - RNCAsyncStorage 174 175+@interface RNCAsyncStorage () 176+ 177+@property (nonatomic, copy) NSString *storageDirectory; 178+@property (nonatomic, copy) NSString *manifestFilePath; 179+ 180+@end 181+ 182 @implementation RNCAsyncStorage { 183 BOOL _haveSetup; 184 // The manifest is a dictionary of all keys with small values inlined. Null values indicate 185 // values that are stored in separate files (as opposed to nil values which don't exist). The 186 // manifest is read off disk at startup, and written to disk after all mutations. 187 NSMutableDictionary<NSString *, NSString *> *_manifest; 188+ NSCache *_cache; 189+ dispatch_once_t _cacheOnceToken; 190+} 191+ 192+// NOTE(nikki93): Prevents the module from being auto-initialized and allows us to pass our own `storageDirectory` 193++ (NSString *)moduleName { return @"RCTAsyncLocalStorage"; } 194+- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory 195+{ 196+ if ((self = [super init])) { 197+ _storageDirectory = storageDirectory; 198+ _manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName]; 199+ } 200+ return self; 201+} 202+ 203+// NOTE(nikki93): Use the default `methodQueue` since instances have different storage directories 204+@synthesize methodQueue = _methodQueue; 205+ 206+- (NSCache *)cache 207+{ 208+ dispatch_once(&_cacheOnceToken, ^{ 209+ _cache = [NSCache new]; 210+ _cache.totalCostLimit = 2 * 1024 * 1024; // 2MB 211+ 212+ // Clear cache in the event of a memory warning 213+ [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) { 214+ [_cache removeAllObjects]; 215+ }]; 216+ }); 217+ return _cache; 218 } 219 220 + (BOOL)requiresMainQueueSetup 221@@ -421,6 +388,7 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory, 222 223 - (instancetype)init 224 { 225+ assert(false); 226 if (!(self = [super init])) { 227 return nil; 228 } 229@@ -444,13 +412,6 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory, 230 return self; 231 } 232 233-RCT_EXPORT_MODULE() 234- 235-- (dispatch_queue_t)methodQueue 236-{ 237- return RCTGetMethodQueue(); 238-} 239- 240 - (void)clearAllData 241 { 242 dispatch_async(RCTGetMethodQueue(), ^{ 243@@ -460,14 +421,6 @@ RCT_EXPORT_MODULE() 244 }); 245 } 246 247-+ (void)clearAllData 248-{ 249- dispatch_async(RCTGetMethodQueue(), ^{ 250- [RCTGetCache() removeAllObjects]; 251- RCTDeleteStorageDirectory(); 252- }); 253-} 254- 255 - (void)invalidate 256 { 257 if (_clearOnInvalidate) { 258@@ -505,12 +458,13 @@ RCT_EXPORT_MODULE() 259 #endif 260 261 NSError *error = nil; 262- if (!RCTHasCreatedStorageDirectory) { 263- _createStorageDirectory(RCTGetStorageDirectory(), &error); 264- if (error) { 265- return RCTMakeError(@"Failed to create storage directory.", error, nil); 266- } 267- RCTHasCreatedStorageDirectory = YES; 268+ // NOTE(nikki93): `withIntermediateDirectories:YES` makes this idempotent 269+ [[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory() 270+ withIntermediateDirectories:YES 271+ attributes:nil 272+ error:&error]; 273+ if (error) { 274+ return RCTMakeError(@"Failed to create storage directory.", error, nil); 275 } 276 277 if (!_haveSetup) { 278@@ -521,11 +475,14 @@ RCT_EXPORT_MODULE() 279 // by default, we want to exclude AsyncStorage data from backup 280 isExcludedFromBackup = @YES; 281 } 282- RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory), 283- isExcludedFromBackup); 284+ // NOTE(kudo): We don't enable iCloud backup for Expo Go 285+ // RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory), 286+ // isExcludedFromBackup); 287 288 NSDictionary *errorOut = nil; 289- NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()), 290+ // NOTE(kudo): Keep data in Documents rather than Application Support for backward compatibility 291+ // NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()) 292+ NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), 293 RCTManifestFileName, 294 &errorOut); 295 if (!serialized) { 296@@ -561,7 +518,9 @@ RCT_EXPORT_MODULE() 297 { 298 NSError *error; 299 NSString *serialized = RCTJSONStringify(_manifest, &error); 300- [serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()) 301+ // NOTE(kudo): Keep data in Documents rather than Application Support for backward compatibility 302+ // [serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()) 303+ [serialized writeToFile:RCTGetManifestFilePath() 304 atomically:YES 305 encoding:NSUTF8StringEncoding 306 error:&error]; 307