[ReactNative] OSS AsyncStorage with example
This commit is contained in:
parent
97f3c5a42b
commit
221ddd3cfe
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
AsyncStorage,
|
||||
PickerIOS,
|
||||
Text,
|
||||
View
|
||||
} = React;
|
||||
var PickerItemIOS = PickerIOS.Item;
|
||||
|
||||
var STORAGE_KEY = '@AsyncStorageExample:key';
|
||||
var COLORS = ['red', 'orange', 'yellow', 'green', 'blue'];
|
||||
|
||||
var BasicStorageExample = React.createClass({
|
||||
componentDidMount() {
|
||||
AsyncStorage.getItem(STORAGE_KEY, (error, value) => {
|
||||
if (error) {
|
||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
||||
} else if (value !== null) {
|
||||
this.setState({selectedValue: value});
|
||||
this._appendMessage('Recovered selection from disk: ' + value);
|
||||
} else {
|
||||
this._appendMessage('Initialized with no selection on disk.');
|
||||
}
|
||||
});
|
||||
},
|
||||
getInitialState() {
|
||||
return {
|
||||
selectedValue: COLORS[0],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
var color = this.state.selectedValue;
|
||||
return (
|
||||
<View>
|
||||
<PickerIOS
|
||||
selectedValue={color}
|
||||
onValueChange={this._onValueChange}>
|
||||
{COLORS.map((value) => (
|
||||
<PickerItemIOS
|
||||
key={value}
|
||||
value={value}
|
||||
label={value}
|
||||
/>
|
||||
))}
|
||||
</PickerIOS>
|
||||
<Text>
|
||||
{'Selected: '}
|
||||
<Text style={{color}}>
|
||||
{this.state.selectedValue}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text onPress={this._removeStorage}>
|
||||
Press here to remove from storage.
|
||||
</Text>
|
||||
<Text>{' '}</Text>
|
||||
<Text>Messages:</Text>
|
||||
{this.state.messages.map((m) => <Text>{m}</Text>)}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_onValueChange(selectedValue) {
|
||||
this.setState({selectedValue});
|
||||
AsyncStorage.setItem(STORAGE_KEY, selectedValue, (error) => {
|
||||
if (error) {
|
||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
||||
} else {
|
||||
this._appendMessage('Saved selection to disk: ' + selectedValue);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_removeStorage() {
|
||||
AsyncStorage.removeItem(STORAGE_KEY, (error) => {
|
||||
if (error) {
|
||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
||||
} else {
|
||||
this._appendMessage('Selection removed from disk.');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_appendMessage(message) {
|
||||
this.setState({messages: this.state.messages.concat(message)});
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = 'AsyncStorage';
|
||||
exports.description = 'Asynchronous local disk storage.';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Basics - getItem, setItem, removeItem',
|
||||
render() { return <BasicStorageExample />; }
|
||||
},
|
||||
];
|
|
@ -16,6 +16,7 @@ var {
|
|||
|
||||
|
||||
var UIExplorerApp = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<NavigatorIOS
|
||||
|
@ -25,7 +26,8 @@ var UIExplorerApp = React.createClass({
|
|||
component: UIExplorerList,
|
||||
}}
|
||||
itemWrapperStyle={styles.itemWrapper}
|
||||
tintColor='#008888'/>
|
||||
tintColor='#008888'
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ var EXAMPLES = [
|
|||
require('./TabBarExample'),
|
||||
require('./SwitchExample'),
|
||||
require('./SliderExample'),
|
||||
require('./AsyncStorageExample'),
|
||||
require('./CameraRollExample.ios'),
|
||||
require('./MapViewExample'),
|
||||
require('./AdSupportIOSExample'),
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule AsyncStorage
|
||||
* @flow-weak
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeModules = require('NativeModulesDeprecated');
|
||||
var RKAsyncLocalStorage = NativeModules.RKAsyncLocalStorage;
|
||||
var RKAsyncRocksDBStorage = NativeModules.RKAsyncRocksDBStorage;
|
||||
|
||||
// We use RocksDB if available.
|
||||
var RKAsyncStorage = RKAsyncRocksDBStorage || RKAsyncLocalStorage;
|
||||
|
||||
/**
|
||||
* AsyncStorage is a simple, asynchronous, persistent, global, key-value storage
|
||||
* system. It should be used instead of LocalStorage.
|
||||
*
|
||||
* It is recommended that you use an abstraction on top of AsyncStorage instead
|
||||
* of AsyncStorage directly for anything more than light usage since it
|
||||
* operates globally.
|
||||
*
|
||||
* This JS code is a simple facad over the native iOS implementation to provide
|
||||
* a clear JS API, real Error objects, and simple non-multi functions.
|
||||
*/
|
||||
var AsyncStorage = {
|
||||
/**
|
||||
* Fetches `key` and passes the result to `callback`, along with an `Error` if
|
||||
* there is any.
|
||||
*/
|
||||
getItem: function(
|
||||
key: string,
|
||||
callback: (error: ?Error, result: ?string) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiGet([key], function(errors, result) {
|
||||
// Unpack result to get value from [[key,value]]
|
||||
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
||||
callback((errors && convertError(errors[0])) || null, value);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets `value` for `key` and calls `callback` on completion, along with an
|
||||
* `Error` if there is any.
|
||||
*/
|
||||
setItem: function(
|
||||
key: string,
|
||||
value: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiSet([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
});
|
||||
},
|
||||
|
||||
removeItem: function(
|
||||
key: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiRemove([key], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges existing value with input value, assuming they are stringified json.
|
||||
*
|
||||
* Not supported by all native implementations.
|
||||
*/
|
||||
mergeItem: function(
|
||||
key: string,
|
||||
value: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiMerge([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Erases *all* AsyncStorage for all clients, libraries, etc. You probably
|
||||
* don't want to call this - use removeItem or multiRemove to clear only your
|
||||
* own keys instead.
|
||||
*/
|
||||
clear: function(callback: ?(error: ?Error) => void) {
|
||||
RKAsyncStorage.clear(function(error) {
|
||||
callback && callback(convertError(error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets *all* keys known to the system, for all callers, libraries, etc.
|
||||
*/
|
||||
getAllKeys: function(callback: (error: ?Error) => void) {
|
||||
RKAsyncStorage.getAllKeys(function(error, keys) {
|
||||
callback(convertError(error), keys);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The following batched functions are useful for executing a lot of
|
||||
* operations at once, allowing for native optimizations and provide the
|
||||
* convenience of a single callback after all operations are complete.
|
||||
*
|
||||
* These functions return arrays of errors, potentially one for every key.
|
||||
* For key-specific errors, the Error object will have a key property to
|
||||
* indicate which key caused the error.
|
||||
*/
|
||||
|
||||
/**
|
||||
* multiGet invokes callback with an array of key-value pair arrays that
|
||||
* matches the input format of multiSet.
|
||||
*
|
||||
* multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
multiGet: function(
|
||||
keys: Array<string>,
|
||||
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiGet(keys, function(errors, result) {
|
||||
callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null,
|
||||
result
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* multiSet and multiMerge take arrays of key-value array pairs that match
|
||||
* the output of multiGet, e.g.
|
||||
*
|
||||
* multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
|
||||
*/
|
||||
multiSet: function(
|
||||
keyValuePairs: Array<Array<string>>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete all the keys in the `keys` array.
|
||||
*/
|
||||
multiRemove: function(
|
||||
keys: Array<string>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiRemove(keys, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges existing values with input values, assuming they are stringified
|
||||
* json.
|
||||
*
|
||||
* Not supported by all native implementations.
|
||||
*/
|
||||
multiMerge: function(
|
||||
keyValuePairs: Array<Array<string>>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RKAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Not all native implementations support merge.
|
||||
if (!RKAsyncStorage.multiMerge) {
|
||||
delete AsyncStorage.mergeItem;
|
||||
delete AsyncStorage.multiMerge;
|
||||
}
|
||||
|
||||
function convertError(error) {
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
var out = new Error(error.message);
|
||||
out.key = error.key; // flow doesn't like this :(
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = AsyncStorage;
|
|
@ -8,20 +8,21 @@
|
|||
var ReactNative = {
|
||||
...require('React'),
|
||||
Animation: require('Animation'),
|
||||
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
|
||||
AppRegistry: require('AppRegistry'),
|
||||
AsyncStorage: require('AsyncStorage'),
|
||||
CameraRoll: require('CameraRoll'),
|
||||
DatePickerIOS: require('DatePickerIOS'),
|
||||
ExpandingText: require('ExpandingText'),
|
||||
MapView: require('MapView'),
|
||||
Image: require('Image'),
|
||||
LayoutAnimation: require('LayoutAnimation'),
|
||||
ListView: require('ListView'),
|
||||
ListViewDataSource: require('ListViewDataSource'),
|
||||
MapView: require('MapView'),
|
||||
NavigatorIOS: require('NavigatorIOS'),
|
||||
PickerIOS: require('PickerIOS'),
|
||||
PixelRatio: require('PixelRatio'),
|
||||
ScrollView: require('ScrollView'),
|
||||
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
|
||||
Slider: require('Slider'),
|
||||
StatusBarIOS: require('StatusBarIOS'),
|
||||
StyleSheet: require('StyleSheet'),
|
||||
|
|
|
@ -41,6 +41,11 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
|
|||
// Enumerate all classes that conform to NSObject protocol
|
||||
void RCTEnumerateClasses(void (^block)(Class cls));
|
||||
|
||||
// Creates a standardized error object
|
||||
// TODO(#6472857): create NSErrors and automatically convert them over the bridge.
|
||||
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -194,3 +194,22 @@ void RCTEnumerateClasses(void (^block)(Class cls))
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData)
|
||||
{
|
||||
if (toStringify) {
|
||||
message = [NSString stringWithFormat:@"%@%@", message, toStringify];
|
||||
}
|
||||
NSMutableDictionary *error = [@{@"message": message} mutableCopy];
|
||||
if (extraData) {
|
||||
[error addEntriesFromDictionary:extraData];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData)
|
||||
{
|
||||
id error = RCTMakeError(message, toStringify, extraData);
|
||||
RCTLogError(@"\nError: %@", error);
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
/**
|
||||
* A simple, asynchronous, persistent, key-value storage system designed as a
|
||||
* backend to the AsyncStorage JS module, which is modeled after LocalStorage.
|
||||
*
|
||||
* Current implementation stores small values in serialized dictionary and
|
||||
* larger values in separate files. Since we use a serial file queue
|
||||
* `RKFileQueue`, reading/writing from multiple threads should be perceived as
|
||||
* being atomic, unless someone bypasses the `RCTAsyncLocalStorage` API.
|
||||
*
|
||||
* Keys and values must always be strings or an error is returned.
|
||||
*/
|
||||
@interface RCTAsyncLocalStorage : NSObject <RCTBridgeModule>
|
||||
|
||||
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)clear:(RCTResponseSenderBlock)callback;
|
||||
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
|
||||
|
||||
@end
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAsyncLocalStorage.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <CommonCrypto/CommonCryptor.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static NSString *const kStorageDir = @"RCTAsyncLocalStorage_V1";
|
||||
static NSString *const kManifestFilename = @"manifest.json";
|
||||
static const NSUInteger kInlineValueThreshold = 100;
|
||||
|
||||
#pragma mark - Static helper functions
|
||||
|
||||
static id RCTErrorForKey(NSString *key)
|
||||
{
|
||||
if (![key isKindOfClass:[NSString class]]) {
|
||||
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key": key});
|
||||
} else if (key.length < 1) {
|
||||
return RCTMakeAndLogError(@"Invalid key - must be at least one character. Key: ", key, @{@"key": key});
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
static void RCTAppendError(id error, NSMutableArray **errors)
|
||||
{
|
||||
if (error && errors) {
|
||||
if (!*errors) {
|
||||
*errors = [NSMutableArray new];
|
||||
}
|
||||
[*errors addObject:error];
|
||||
}
|
||||
}
|
||||
|
||||
static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
|
||||
{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
NSError *error;
|
||||
NSStringEncoding encoding;
|
||||
NSString *entryString = [NSString stringWithContentsOfFile:filePath usedEncoding:&encoding error:&error];
|
||||
if (error) {
|
||||
*errorOut = RCTMakeError(@"Failed to read storage file.", error, @{@"key": key});
|
||||
} else if (encoding != NSUTF8StringEncoding) {
|
||||
*errorOut = RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), @{@"key": key});
|
||||
} else {
|
||||
return entryString;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static dispatch_queue_t RCTFileQueue(void)
|
||||
{
|
||||
static dispatch_queue_t fileQueue = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// All JS is single threaded, so a serial queue is our only option.
|
||||
fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_set_target_queue(fileQueue,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
||||
});
|
||||
|
||||
return fileQueue;
|
||||
}
|
||||
|
||||
#pragma mark - RCTAsyncLocalStorage
|
||||
|
||||
@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 *_manifest;
|
||||
NSString *_manifestPath;
|
||||
NSString *_storageDirectory;
|
||||
}
|
||||
|
||||
- (NSString *)_filePathForKey:(NSString *)key
|
||||
{
|
||||
NSString *safeFileName = RCTMD5Hash(key);
|
||||
return [_storageDirectory stringByAppendingPathComponent:safeFileName];
|
||||
}
|
||||
|
||||
- (id)_ensureSetup
|
||||
{
|
||||
if (_haveSetup) {
|
||||
return nil;
|
||||
}
|
||||
NSString *documentDirectory =
|
||||
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES];
|
||||
_storageDirectory = [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path];
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&error];
|
||||
if (error) {
|
||||
return RCTMakeError(@"Failed to create storage directory.", error, nil);
|
||||
}
|
||||
_manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename];
|
||||
NSDictionary *errorOut;
|
||||
NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut);
|
||||
_manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new];
|
||||
if (error) {
|
||||
RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error);
|
||||
_manifest = [NSMutableDictionary new];
|
||||
}
|
||||
_haveSetup = YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)_writeManifest:(NSMutableArray **)errors
|
||||
{
|
||||
NSError *error;
|
||||
NSString *serialized = RCTJSONStringify(_manifest, &error);
|
||||
[serialized writeToFile:_manifestPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
id errorOut;
|
||||
if (error) {
|
||||
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
|
||||
RCTAppendError(errorOut, errors);
|
||||
}
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
- (id)_appendItemForKey:(NSString *)key toArray:(NSMutableArray *)result
|
||||
{
|
||||
id errorOut = RCTErrorForKey(key);
|
||||
if (errorOut) {
|
||||
return errorOut;
|
||||
}
|
||||
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
|
||||
if (value == [NSNull null]) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
value = RCTReadFile(filePath, key, &errorOut);
|
||||
}
|
||||
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
- (id)_writeEntry:(NSArray *)entry
|
||||
{
|
||||
if (![entry isKindOfClass:[NSArray class]] || entry.count != 2) {
|
||||
return RCTMakeAndLogError(@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
|
||||
}
|
||||
if (![entry[1] isKindOfClass:[NSString class]]) {
|
||||
return RCTMakeAndLogError(@"Values must be strings, got: ", entry[1], entry[0]);
|
||||
}
|
||||
NSString *key = entry[0];
|
||||
id errorOut = RCTErrorForKey(key);
|
||||
if (errorOut) {
|
||||
return errorOut;
|
||||
}
|
||||
NSString *value = entry[1];
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
NSError *error;
|
||||
if (value.length <= kInlineValueThreshold) {
|
||||
if (_manifest[key] && _manifest[key] != [NSNull null]) {
|
||||
// If the value already existed but wasn't inlined, remove the old file.
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||
}
|
||||
_manifest[key] = value;
|
||||
return nil;
|
||||
}
|
||||
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
|
||||
} else {
|
||||
_manifest[key] = [NSNull null]; // Mark existence of file with null, any other value is inline data.
|
||||
}
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
#pragma mark - Exported JS Functions
|
||||
|
||||
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
if (!callback) {
|
||||
RCTLogError(@"Called getItem without a callback.");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut], [NSNull null]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
|
||||
for (NSString *key in keys) {
|
||||
id keyError = [self _appendItemForKey:key toArray:result];
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
callback(@[errors ?: [NSNull null], result]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
for (NSArray *entry in kvPairs) {
|
||||
id keyError = [self _writeEntry:entry];
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
if (callback) {
|
||||
callback(@[errors ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
for (NSString *key in keys) {
|
||||
id keyError = RCTErrorForKey(key);
|
||||
if (!keyError) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||
[_manifest removeObjectForKey:key];
|
||||
}
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
if (callback) {
|
||||
callback(@[errors ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)clear:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (!errorOut) {
|
||||
NSError *error;
|
||||
for (NSString *key in _manifest) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
||||
}
|
||||
[_manifest removeAllObjects];
|
||||
errorOut = [self _writeManifest:nil];
|
||||
}
|
||||
if (callback) {
|
||||
callback(@[errorOut ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)getAllKeys:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[errorOut, [NSNull null]]);
|
||||
} else {
|
||||
callback(@[[NSNull null], [_manifest allKeys]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
|
@ -42,6 +42,7 @@
|
|||
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; };
|
||||
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
|
||||
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; };
|
||||
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; };
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
|
||||
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
|
||||
|
@ -147,6 +148,8 @@
|
|||
58114A151AAE854800E7D092 /* RCTPickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPickerManager.m; sourceTree = "<group>"; };
|
||||
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = "<group>"; };
|
||||
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = "<group>"; };
|
||||
58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAsyncLocalStorage.m; sourceTree = "<group>"; };
|
||||
58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAsyncLocalStorage.h; sourceTree = "<group>"; };
|
||||
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
|
||||
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
|
||||
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
|
||||
|
@ -215,6 +218,8 @@
|
|||
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
|
||||
83C9110E1AAE6521001323A3 /* RCTAnimationManager.h */,
|
||||
83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */,
|
||||
58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */,
|
||||
58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */,
|
||||
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
|
||||
13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */,
|
||||
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */,
|
||||
|
@ -439,6 +444,7 @@
|
|||
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
||||
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
||||
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
|
||||
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,
|
||||
832348161A77A5AA00B55238 /* Layout.c in Sources */,
|
||||
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
|
||||
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
|
||||
|
|
|
@ -8,4 +8,7 @@
|
|||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, copy) NSArray *items;
|
||||
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue