diff --git a/README.md b/README.md index f2d7a966..2d6b79bd 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-${userId}-storage`, path: `${USER_DIRECTORY}/storage`, - encryptionKey: 'hunter2' + encryptionKey: 'hunter2', + mode: 'single-mode' }) ``` @@ -124,7 +125,7 @@ The following values can be configured: * `id`: The MMKV instance's ID. If you want to use multiple instances, use different IDs. For example, you can separate the global app's storage and a logged-in user's storage. (required if `path` or `encryptionKey` fields are specified, otherwise defaults to: `'mmkv.default'`) * `path`: The MMKV instance's root path. By default, MMKV stores file inside `$(Documents)/mmkv/`. You can customize MMKV's root directory on MMKV initialization (documentation: [iOS](https://github.com/Tencent/MMKV/wiki/iOS_advance#customize-location) / [Android](https://github.com/Tencent/MMKV/wiki/android_advance#customize-location)) * `encryptionKey`: The MMKV instance's encryption/decryption key. By default, MMKV stores all key-values in plain text on file, relying on iOS's/Android's sandbox to make sure the file is encrypted. Should you worry about information leaking, you can choose to encrypt MMKV. (documentation: [iOS](https://github.com/Tencent/MMKV/wiki/iOS_advance#encryption) / [Android](https://github.com/Tencent/MMKV/wiki/android_advance#encryption)) - +- `mode`: The MMKV mode. You can set its value to `multi-process` to support simultaneous read-write access between process at the cost of performance. This is useful when you want to share data between your react-native app and native extensions such as widgets. On iOS additionally you need to set the AppGroup bundle property to share the same storage between your app and its extensions. If you don't set the AppGroup, the mode will be ignored and the mmkv will use the default storage location. More information on AppGroups [here](https://github.com/mrousavy/react-native-mmkv/tree/master#app-groups). For backward compatibility, the mode is set to `single-process` on android and for iOS it'll use `multi-process` if `AppGroup` exists. ### Set ```js diff --git a/android/src/main/cpp/MmkvHostObject.cpp b/android/src/main/cpp/MmkvHostObject.cpp index 1ffbdc2d..7651cc1f 100644 --- a/android/src/main/cpp/MmkvHostObject.cpp +++ b/android/src/main/cpp/MmkvHostObject.cpp @@ -14,15 +14,14 @@ #include MmkvHostObject::MmkvHostObject(const std::string& instanceId, std::string path, - std::string cryptKey) { + std::string cryptKey, MMKVMode mmkvMode) { bool hasEncryptionKey = cryptKey.size() > 0; __android_log_print(ANDROID_LOG_INFO, "RNMMKV", "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %b)", instanceId.c_str(), path.c_str(), hasEncryptionKey); std::string* pathPtr = path.size() > 0 ? &path : nullptr; std::string* cryptKeyPtr = cryptKey.size() > 0 ? &cryptKey : nullptr; - instance = MMKV::mmkvWithID(instanceId, mmkv::DEFAULT_MMAP_SIZE, MMKV_SINGLE_PROCESS, cryptKeyPtr, - pathPtr); + instance = MMKV::mmkvWithID(instanceId, mmkv::DEFAULT_MMAP_SIZE, mmkvMode, cryptKeyPtr, pathPtr); if (instance == nullptr) { // Check if instanceId is invalid diff --git a/android/src/main/cpp/MmkvHostObject.h b/android/src/main/cpp/MmkvHostObject.h index b2c5573a..d7e615da 100644 --- a/android/src/main/cpp/MmkvHostObject.h +++ b/android/src/main/cpp/MmkvHostObject.h @@ -15,7 +15,8 @@ using namespace facebook; class JSI_EXPORT MmkvHostObject : public jsi::HostObject { public: - MmkvHostObject(const std::string& instanceId, std::string path, std::string cryptKey); + MmkvHostObject(const std::string& instanceId, std::string path, std::string cryptKey, + MMKVMode mmkvMode); public: jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override; diff --git a/android/src/main/cpp/cpp-adapter.cpp b/android/src/main/cpp/cpp-adapter.cpp index 976a2789..ed07d147 100644 --- a/android/src/main/cpp/cpp-adapter.cpp +++ b/android/src/main/cpp/cpp-adapter.cpp @@ -13,6 +13,18 @@ std::string getPropertyAsStringOrEmptyFromObject(jsi::Object& object, return value.isString() ? value.asString(runtime).utf8(runtime) : ""; } +const std::string MMKV_MULTI_PROCESS_MODE = "multi-process"; +MMKVMode getPropertyAsMMKVModeFromObject(jsi::Object& object, const std::string& propertyName, + jsi::Runtime& runtime) { + std::string value = getPropertyAsStringOrEmptyFromObject(object, propertyName, runtime); + if (value == MMKV_MULTI_PROCESS_MODE) { + return MMKV_MULTI_PROCESS; + } + + // Use Single Process as default value + return MMKV_SINGLE_PROCESS; +} + void install(jsi::Runtime& jsiRuntime) { // MMKV.createNewInstance() auto mmkvCreateNewInstance = jsi::Function::createFromHostFunction( @@ -28,8 +40,9 @@ void install(jsi::Runtime& jsiRuntime) { std::string path = getPropertyAsStringOrEmptyFromObject(config, "path", runtime); std::string encryptionKey = getPropertyAsStringOrEmptyFromObject(config, "encryptionKey", runtime); + MMKVMode mode = getPropertyAsMMKVModeFromObject(config, "mode", runtime); - auto instance = std::make_shared(instanceId, path, encryptionKey); + auto instance = std::make_shared(instanceId, path, encryptionKey, mode); return jsi::Object::createFromHostObject(runtime, instance); }); jsiRuntime.global().setProperty(jsiRuntime, "mmkvCreateNewInstance", diff --git a/example/ios/Podfile b/example/ios/Podfile index 28b605df..0c3861dd 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -10,6 +10,22 @@ if linkage != nil use_frameworks! :linkage => linkage.to_sym end +def __apply_Xcode_14_3_RC_post_install_workaround(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4' + end + end +end + +def __apply_Xcode_15_0_RC_post_install_workaround(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION'] + end + end +end + target 'MmkvExample' do config = use_native_modules! @@ -42,5 +58,7 @@ target 'MmkvExample' do :mac_catalyst_enabled => false ) __apply_Xcode_12_5_M1_post_install_workaround(installer) + __apply_Xcode_14_3_RC_post_install_workaround(installer) + __apply_Xcode_15_0_RC_post_install_workaround(installer) end end diff --git a/ios/MmkvHostObject.h b/ios/MmkvHostObject.h index 906156d9..5fe977bf 100644 --- a/ios/MmkvHostObject.h +++ b/ios/MmkvHostObject.h @@ -16,7 +16,7 @@ using namespace facebook; class JSI_EXPORT MmkvHostObject : public jsi::HostObject { public: - MmkvHostObject(NSString* instanceId, NSString* path, NSString* cryptKey); + MmkvHostObject(NSString* instanceId, NSString* path, NSString* cryptKey, NSString* mode); public: jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override; diff --git a/ios/MmkvHostObject.mm b/ios/MmkvHostObject.mm index 16045fd7..5d47a67e 100644 --- a/ios/MmkvHostObject.mm +++ b/ios/MmkvHostObject.mm @@ -12,18 +12,25 @@ #import #import -MmkvHostObject::MmkvHostObject(NSString* instanceId, NSString* path, NSString* cryptKey) { +MmkvHostObject::MmkvHostObject(NSString* instanceId, NSString* path, NSString* cryptKey, + NSString* mode) { NSData* cryptData = cryptKey == nil ? nil : [cryptKey dataUsingEncoding:NSUTF8StringEncoding]; // Get appGroup value from info.plist using key "AppGroup" NSString* appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; - if (appGroup == nil) { + if ([mode isEqualToString:@"single-process"]) { + if (appGroup != nil) { + NSLog(@"Warning: `mode` is set to 'single-process' but `appGroup` is also set in bundle! " + @"Ignoring `app groups` from bundle and using `mode` instead!"); + } instance = [MMKV mmkvWithID:instanceId cryptKey:cryptData rootPath:path]; - } else { + } else if (appGroup != nil) { if (path != nil) { NSLog(@"Warning: `path` is ignored when `appGroup` is set!"); } instance = [MMKV mmkvWithID:instanceId cryptKey:cryptData mode:MMKVMultiProcess]; + } else { + instance = [MMKV mmkvWithID:instanceId cryptKey:cryptData rootPath:path]; } if (instance == nil) { diff --git a/ios/MmkvModule.mm b/ios/MmkvModule.mm index 85f93830..a237cc6b 100644 --- a/ios/MmkvModule.mm +++ b/ios/MmkvModule.mm @@ -50,24 +50,11 @@ + (NSString*)getPropertyAsStringOrNilFromObject:(jsi::Object&)object MMKVLogLevel logLevel = MMKVLogError; #endif - RCTUnsafeExecuteOnMainQueueSync(^{ - // Get appGroup value from info.plist using key "AppGroup" - NSString* appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; - if (appGroup == nil) { - [MMKV initializeMMKV:storageDirectory logLevel:logLevel]; - } else { - NSString* groupDir = [[NSFileManager defaultManager] - containerURLForSecurityApplicationGroupIdentifier:appGroup] - .path; - [MMKV initializeMMKV:nil groupDir:groupDir logLevel:logLevel]; - } - }); - // MMKV.createNewInstance() auto mmkvCreateNewInstance = jsi::Function::createFromHostFunction( runtime, jsi::PropNameID::forAscii(runtime, "mmkvCreateNewInstance"), 1, - [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, - size_t count) -> jsi::Value { + [=](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, + size_t count) -> jsi::Value { if (count != 1) { throw jsi::JSError(runtime, "MMKV.createNewInstance(..) expects one argument (object)!"); } @@ -82,8 +69,25 @@ + (NSString*)getPropertyAsStringOrNilFromObject:(jsi::Object&)object NSString* encryptionKey = [MmkvModule getPropertyAsStringOrNilFromObject:config propertyName:"encryptionKey" runtime:runtime]; + NSString* mode = [MmkvModule getPropertyAsStringOrNilFromObject:config + propertyName:"mode" + runtime:runtime]; - auto instance = std::make_shared(instanceId, path, encryptionKey); + RCTUnsafeExecuteOnMainQueueSync(^{ + NSString* appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; + if ([mode isEqualToString:@"single-process"]) { + [MMKV initializeMMKV:storageDirectory logLevel:logLevel]; + } else if (appGroup != nil) { + NSString* groupDir = [[NSFileManager defaultManager] + containerURLForSecurityApplicationGroupIdentifier:appGroup] + .path; + [MMKV initializeMMKV:nil groupDir:groupDir logLevel:logLevel]; + } else { + [MMKV initializeMMKV:storageDirectory logLevel:logLevel]; + } + }); + + auto instance = std::make_shared(instanceId, path, encryptionKey, mode); return jsi::Object::createFromHostObject(runtime, instance); }); runtime.global().setProperty(runtime, "mmkvCreateNewInstance", std::move(mmkvCreateNewInstance)); diff --git a/src/MMKV.ts b/src/MMKV.ts index 973a5e74..c3886a9e 100644 --- a/src/MMKV.ts +++ b/src/MMKV.ts @@ -46,6 +46,21 @@ export interface MMKVConfiguration { * ``` */ encryptionKey?: string; + /** + * The MMKV mode. You can set its value to `multi-process` to support simultaneous read-write access between process at the cost of performance. + * + * This is useful when you want to share data between your react-native app and native extensions such as widgets. + * + * @example + * ```ts + * const extensionStorage = new MMKV({ id: 'mmkv.default', mode: 'multi-process' }) + * ``` + * + * _Notice_: on iOS additionally you need to set the AppGroup bundle property to share the same storage between your app and its extensions. If you don't set the AppGroup, the mode will be ignored and the mmkv will use the default storage location. + * More information on AppGroups [here](https://github.com/mrousavy/react-native-mmkv/tree/master#app-groups) + * _Notice_: for backward compatibility, the mode is set to `single-process` on android and for iOS it'll use `multi-process` if `AppGroup` exists. + */ + mode?: 'single-process' | 'multi-process'; } /**