Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi-process mode #639

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
})
```

Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions android/src/main/cpp/MmkvHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
#include <vector>

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
Expand Down
3 changes: 2 additions & 1 deletion android/src/main/cpp/MmkvHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 14 additions & 1 deletion android/src/main/cpp/cpp-adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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<MmkvHostObject>(instanceId, path, encryptionKey);
auto instance = std::make_shared<MmkvHostObject>(instanceId, path, encryptionKey, mode);
return jsi::Object::createFromHostObject(runtime, instance);
});
jsiRuntime.global().setProperty(jsiRuntime, "mmkvCreateNewInstance",
Expand Down
18 changes: 18 additions & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -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!

Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion ios/MmkvHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 10 additions & 3 deletions ios/MmkvHostObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@
#import <Foundation/Foundation.h>
#import <vector>

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) {
Expand Down
36 changes: 20 additions & 16 deletions ios/MmkvModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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)!");
}
Expand All @@ -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<MmkvHostObject>(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<MmkvHostObject>(instanceId, path, encryptionKey, mode);
return jsi::Object::createFromHostObject(runtime, instance);
});
runtime.global().setProperty(runtime, "mmkvCreateNewInstance", std::move(mmkvCreateNewInstance));
Expand Down
15 changes: 15 additions & 0 deletions src/MMKV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

/**
Expand Down
Loading