verdnatura-chat/ios/Pods/MMKVAppExtension/iOS/MMKV/MMKV/libMMKV.mm

700 lines
22 KiB
Plaintext

/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MMKV.h"
#import <MMKVCore/MMKV.h>
#import <MMKVCore/MMKVLog.h>
#import <MMKVCore/ScopedLock.hpp>
#import <MMKVCore/ThreadLock.h>
#import <MMKVCore/openssl_md5.h>
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
#import <UIKit/UIKit.h>
#endif
using namespace std;
static NSMutableDictionary *g_instanceDic = nil;
static mmkv::ThreadLock *g_lock;
static id<MMKVHandler> g_callbackHandler = nil;
static bool g_isLogRedirecting = false;
static NSString *g_basePath = nil;
static NSString *g_groupPath = nil;
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
static BOOL g_isRunningInAppExtension = NO;
#endif
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message);
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType);
static void ContentChangeHandler(const string &mmapID);
@implementation MMKV {
NSString *m_mmapID;
NSString *m_mmapKey;
mmkv::MMKV *m_mmkv;
}
#pragma mark - init
// protect from some old code that don't call +initializeMMKV:
+ (void)initialize {
if (self == MMKV.class) {
g_instanceDic = [[NSMutableDictionary alloc] init];
g_lock = new mmkv::ThreadLock();
g_lock->initialize();
mmkv::MMKV::minimalInit([self mmkvBasePath].UTF8String);
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
// just in case someone forget to set the MMKV_IOS_EXTENSION macro
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) {
g_isRunningInAppExtension = YES;
}
if (!g_isRunningInAppExtension) {
auto appState = [UIApplication sharedApplication].applicationState;
auto isInBackground = (appState == UIApplicationStateBackground);
mmkv::MMKV::setIsInBackground(isInBackground);
MMKVInfo("appState:%ld", (long) appState);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}
#endif
}
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir {
return [MMKV initializeMMKV:rootDir logLevel:MMKVLogInfo];
}
static BOOL g_hasCalledInitializeMMKV = NO;
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir logLevel:(MMKVLogLevel)logLevel {
if (g_hasCalledInitializeMMKV) {
MMKVWarning("already called +initializeMMKV before, ignore this request");
return [self mmkvBasePath];
}
g_hasCalledInitializeMMKV = YES;
g_basePath = (rootDir != nil) ? rootDir : [self mmkvBasePath];
mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel);
return [self mmkvBasePath];
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir groupDir:(NSString *)groupDir logLevel:(MMKVLogLevel)logLevel {
auto ret = [MMKV initializeMMKV:rootDir logLevel:logLevel];
g_groupPath = [groupDir stringByAppendingPathComponent:@"mmkv"];
MMKVInfo("groupDir: %@", g_groupPath);
return ret;
}
// a generic purpose instance
+ (instancetype)defaultMMKV {
return [MMKV mmkvWithID:(@"" DEFAULT_MMAP_ID) cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
// any unique ID (com.tencent.xin.pay, etc)
+ (instancetype)mmkvWithID:(NSString *)mmapID {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:relativePath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:relativePath mode:MMKVSingleProcess];
}
// relatePath and MMKVMultiProcess mode can't be set at the same time, so we hide this method from public
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath mode:(MMKVMode)mode {
if (!g_hasCalledInitializeMMKV) {
MMKVError("MMKV not initialized properly, must call +initializeMMKV: in main thread before calling any other MMKV methods");
}
if (mmapID.length <= 0) {
return nil;
}
SCOPED_LOCK(g_lock);
if (mode == MMKVMultiProcess) {
if (!rootPath) {
rootPath = g_groupPath;
}
if (!rootPath) {
MMKVError("Getting a multi-process MMKV [%@] without setting groupDir makes no sense", mmapID);
MMKV_ASSERT(0);
}
}
NSString *kvKey = [MMKV mmapKeyWithMMapID:mmapID rootPath:rootPath];
MMKV *kv = [g_instanceDic objectForKey:kvKey];
if (kv == nil) {
kv = [[MMKV alloc] initWithMMapID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
if (!kv->m_mmkv) {
return nil;
}
kv->m_mmapKey = kvKey;
[g_instanceDic setObject:kv forKey:kvKey];
}
return kv;
}
- (instancetype)initWithMMapID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(NSString *)rootPath mode:(MMKVMode)mode {
if (self = [super init]) {
string pathTmp;
if (rootPath.length > 0) {
pathTmp = rootPath.UTF8String;
}
string cryptKeyTmp;
if (cryptKey.length > 0) {
cryptKeyTmp = string((char *) cryptKey.bytes, cryptKey.length);
}
string *rootPathPtr = pathTmp.empty() ? nullptr : &pathTmp;
string *cryptKeyPtr = cryptKeyTmp.empty() ? nullptr : &cryptKeyTmp;
m_mmkv = mmkv::MMKV::mmkvWithID(mmapID.UTF8String, (mmkv::MMKVMode) mode, cryptKeyPtr, rootPathPtr);
if (!m_mmkv) {
return self;
}
m_mmapID = [NSString stringWithUTF8String:m_mmkv->mmapID().c_str()];
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
if (!g_isRunningInAppExtension) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
#endif
}
return self;
}
- (void)dealloc {
[self clearMemoryCache];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Application state
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
- (void)onMemoryWarning {
MMKVInfo("cleaning on memory warning %@", m_mmapID);
[self clearMemoryCache];
}
+ (void)didEnterBackground {
mmkv::MMKV::setIsInBackground(true);
MMKVInfo("isInBackground:%d", true);
}
+ (void)didBecomeActive {
mmkv::MMKV::setIsInBackground(false);
MMKVInfo("isInBackground:%d", false);
}
#endif
- (void)clearAll {
m_mmkv->clearAll();
}
- (void)clearMemoryCache {
if (m_mmkv) {
m_mmkv->clearMemoryCache();
}
}
- (void)close {
SCOPED_LOCK(g_lock);
MMKVInfo("closing %@", m_mmapID);
m_mmkv->close();
m_mmkv = nullptr;
[g_instanceDic removeObjectForKey:m_mmapKey];
}
- (void)trim {
m_mmkv->trim();
}
#pragma mark - encryption & decryption
#ifndef MMKV_DISABLE_CRYPT
- (nullable NSData *)cryptKey {
auto str = m_mmkv->cryptKey();
if (str.length() > 0) {
return [NSData dataWithBytes:str.data() length:str.length()];
}
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
string key;
if (newKey.length > 0) {
key = string((char *) newKey.bytes, newKey.length);
}
return m_mmkv->reKey(key);
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
if (cryptKey.length > 0) {
string key = string((char *) cryptKey.bytes, cryptKey.length);
m_mmkv->checkReSetCryptKey(&key);
} else {
m_mmkv->checkReSetCryptKey(nullptr);
}
}
#else
- (nullable NSData *)cryptKey {
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
return NO;
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
}
#endif // MMKV_DISABLE_CRYPT
#pragma mark - set & get
- (BOOL)setObject:(nullable NSObject<NSCoding> *)object forKey:(NSString *)key {
return m_mmkv->set(object, key);
}
- (BOOL)setBool:(BOOL)value forKey:(NSString *)key {
return m_mmkv->set((bool) value, key);
}
- (BOOL)setInt32:(int32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt32:(uint32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setInt64:(int64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt64:(uint64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setFloat:(float)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setDouble:(double)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setString:(NSString *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setDate:(NSDate *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setData:(NSData *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (id)getObjectOfClass:(Class)cls forKey:(NSString *)key {
return m_mmkv->getObject(key, cls);
}
- (BOOL)getBoolForKey:(NSString *)key {
return [self getBoolForKey:key defaultValue:FALSE];
}
- (BOOL)getBoolForKey:(NSString *)key defaultValue:(BOOL)defaultValue {
return m_mmkv->getBool(key, defaultValue);
}
- (int32_t)getInt32ForKey:(NSString *)key {
return [self getInt32ForKey:key defaultValue:0];
}
- (int32_t)getInt32ForKey:(NSString *)key defaultValue:(int32_t)defaultValue {
return m_mmkv->getInt32(key, defaultValue);
}
- (uint32_t)getUInt32ForKey:(NSString *)key {
return [self getUInt32ForKey:key defaultValue:0];
}
- (uint32_t)getUInt32ForKey:(NSString *)key defaultValue:(uint32_t)defaultValue {
return m_mmkv->getUInt32(key, defaultValue);
}
- (int64_t)getInt64ForKey:(NSString *)key {
return [self getInt64ForKey:key defaultValue:0];
}
- (int64_t)getInt64ForKey:(NSString *)key defaultValue:(int64_t)defaultValue {
return m_mmkv->getInt64(key, defaultValue);
}
- (uint64_t)getUInt64ForKey:(NSString *)key {
return [self getUInt64ForKey:key defaultValue:0];
}
- (uint64_t)getUInt64ForKey:(NSString *)key defaultValue:(uint64_t)defaultValue {
return m_mmkv->getUInt64(key, defaultValue);
}
- (float)getFloatForKey:(NSString *)key {
return [self getFloatForKey:key defaultValue:0];
}
- (float)getFloatForKey:(NSString *)key defaultValue:(float)defaultValue {
return m_mmkv->getFloat(key, defaultValue);
}
- (double)getDoubleForKey:(NSString *)key {
return [self getDoubleForKey:key defaultValue:0];
}
- (double)getDoubleForKey:(NSString *)key defaultValue:(double)defaultValue {
return m_mmkv->getDouble(key, defaultValue);
}
- (nullable NSString *)getStringForKey:(NSString *)key {
return [self getStringForKey:key defaultValue:nil];
}
- (nullable NSString *)getStringForKey:(NSString *)key defaultValue:(nullable NSString *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSString *valueString = [self getObjectOfClass:NSString.class forKey:key];
if (!valueString) {
valueString = defaultValue;
}
return valueString;
}
- (nullable NSDate *)getDateForKey:(NSString *)key {
return [self getDateForKey:key defaultValue:nil];
}
- (nullable NSDate *)getDateForKey:(NSString *)key defaultValue:(nullable NSDate *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSDate *valueDate = [self getObjectOfClass:NSDate.class forKey:key];
if (!valueDate) {
valueDate = defaultValue;
}
return valueDate;
}
- (nullable NSData *)getDataForKey:(NSString *)key {
return [self getDataForKey:key defaultValue:nil];
}
- (nullable NSData *)getDataForKey:(NSString *)key defaultValue:(nullable NSData *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSData *valueData = [self getObjectOfClass:NSData.class forKey:key];
if (!valueData) {
valueData = defaultValue;
}
return valueData;
}
- (size_t)getValueSizeForKey:(NSString *)key {
return m_mmkv->getValueSize(key, false);
}
- (int32_t)writeValueForKey:(NSString *)key toBuffer:(NSMutableData *)buffer {
return m_mmkv->writeValueToBuffer(key, buffer.mutableBytes, static_cast<int32_t>(buffer.length));
}
#pragma mark - enumerate
- (BOOL)containsKey:(NSString *)key {
return m_mmkv->containsKey(key);
}
- (size_t)count {
return m_mmkv->count();
}
- (size_t)totalSize {
return m_mmkv->totalSize();
}
- (size_t)actualSize {
return m_mmkv->actualSize();
}
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block {
m_mmkv->enumerateKeys(block);
}
- (NSArray *)allKeys {
return m_mmkv->allKeys();
}
- (void)removeValueForKey:(NSString *)key {
m_mmkv->removeValueForKey(key);
}
- (void)removeValuesForKeys:(NSArray *)arrKeys {
m_mmkv->removeValuesForKeys(arrKeys);
}
#pragma mark - Boring stuff
- (void)sync {
m_mmkv->sync(mmkv::MMKV_SYNC);
}
- (void)async {
m_mmkv->sync(mmkv::MMKV_ASYNC);
}
- (void)checkContentChanged {
m_mmkv->checkContentChanged();
}
+ (void)onAppTerminate {
SCOPED_LOCK(g_lock);
[g_instanceDic removeAllObjects];
mmkv::MMKV::onExit();
}
+ (NSString *)mmkvBasePath {
if (g_basePath.length > 0) {
return g_basePath;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = (NSString *) [paths firstObject];
if ([documentPath length] > 0) {
g_basePath = [documentPath stringByAppendingPathComponent:@"mmkv"];
return g_basePath;
} else {
return @"";
}
}
+ (void)setMMKVBasePath:(NSString *)basePath {
if (basePath.length > 0) {
g_basePath = basePath;
[MMKV initializeMMKV:basePath];
// still warn about it
g_hasCalledInitializeMMKV = NO;
MMKVInfo("set MMKV base path to: %@", g_basePath);
}
}
static NSString *md5(NSString *value) {
uint8_t md[MD5_DIGEST_LENGTH] = {};
char tmp[3] = {}, buf[33] = {};
auto data = [value dataUsingEncoding:NSUTF8StringEncoding];
openssl::MD5((uint8_t *) data.bytes, data.length, md);
for (auto ch : md) {
snprintf(tmp, sizeof(tmp), "%2.2x", ch);
strcat(buf, tmp);
}
return [NSString stringWithCString:buf encoding:NSASCIIStringEncoding];
}
+ (NSString *)mmapKeyWithMMapID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
NSString *string = nil;
if ([rootPath length] > 0 && [rootPath isEqualToString:[MMKV mmkvBasePath]] == NO) {
string = md5([rootPath stringByAppendingPathComponent:mmapID]);
} else {
string = mmapID;
}
MMKVDebug("mmapKey: %@", string);
return string;
}
+ (BOOL)isFileValid:(NSString *)mmapID {
return [self isFileValid:mmapID rootPath:nil];
}
+ (BOOL)isFileValid:(NSString *)mmapID rootPath:(nullable NSString *)path {
if (mmapID.length > 0) {
if (path.length > 0) {
string rootPath(path.UTF8String);
return mmkv::MMKV::isFileValid(mmapID.UTF8String, &rootPath);
} else {
return mmkv::MMKV::isFileValid(mmapID.UTF8String, nullptr);
}
}
return NO;
}
+ (void)registerHandler:(id<MMKVHandler>)handler {
SCOPED_LOCK(g_lock);
g_callbackHandler = handler;
if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) {
g_isLogRedirecting = true;
mmkv::MMKV::registerLogHandler(LogHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] ||
[g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
mmkv::MMKV::registerErrorHandler(ErrorHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler);
}
}
+ (void)unregiserHandler {
SCOPED_LOCK(g_lock);
g_isLogRedirecting = false;
g_callbackHandler = nil;
mmkv::MMKV::unRegisterLogHandler();
mmkv::MMKV::unRegisterErrorHandler();
mmkv::MMKV::unRegisterContentChangeHandler();
}
+ (void)setLogLevel:(MMKVLogLevel)logLevel {
mmkv::MMKV::setLogLevel((mmkv::MMKVLogLevel) logLevel);
}
- (uint32_t)migrateFromUserDefaults:(NSUserDefaults *)userDaults {
NSDictionary *dic = [userDaults dictionaryRepresentation];
if (dic.count <= 0) {
MMKVInfo("migrate data fail, userDaults is nil or empty");
return 0;
}
__block uint32_t count = 0;
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
if ([key isKindOfClass:[NSString class]]) {
NSString *stringKey = key;
if ([MMKV tranlateData:obj key:stringKey kv:self]) {
count++;
}
} else {
MMKVWarning("unknown type of key:%@", key);
}
}];
return count;
}
+ (BOOL)tranlateData:(id)obj key:(NSString *)key kv:(MMKV *)kv {
if ([obj isKindOfClass:[NSString class]]) {
return [kv setString:obj forKey:key];
} else if ([obj isKindOfClass:[NSData class]]) {
return [kv setData:obj forKey:key];
} else if ([obj isKindOfClass:[NSDate class]]) {
return [kv setDate:obj forKey:key];
} else if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber *num = obj;
CFNumberType numberType = CFNumberGetType((CFNumberRef) obj);
switch (numberType) {
case kCFNumberCharType:
case kCFNumberSInt8Type:
case kCFNumberSInt16Type:
case kCFNumberSInt32Type:
case kCFNumberIntType:
case kCFNumberShortType:
return [kv setInt32:num.intValue forKey:key];
case kCFNumberSInt64Type:
case kCFNumberLongType:
case kCFNumberNSIntegerType:
case kCFNumberLongLongType:
return [kv setInt64:num.longLongValue forKey:key];
case kCFNumberFloat32Type:
return [kv setFloat:num.floatValue forKey:key];
case kCFNumberFloat64Type:
case kCFNumberDoubleType:
return [kv setDouble:num.doubleValue forKey:key];
default:
MMKVWarning("unknown number type:%ld, key:%@", (long) numberType, key);
return NO;
}
} else if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) {
return [kv setObject:obj forKey:key];
} else {
MMKVWarning("unknown type of key:%@", key);
}
return NO;
}
@end
#pragma makr - callbacks
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message) {
[g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message];
}
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType) {
if (errorType == mmkv::MMKVCRCCheckFail) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) {
auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
} else {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
}
return mmkv::OnErrorDiscard;
}
static void ContentChangeHandler(const string &mmapID) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
[g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]];
}
}