diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h index 2050b7bcee89..62f21d37cc00 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h @@ -16,8 +16,11 @@ #import #import #import +#import #import +#import #import +#import #define RCT_IS_TURBO_MODULE_CLASS(klass) \ ((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)])) @@ -132,14 +135,27 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { // Does the NativeModule dispatch async methods to the JS thread? const bool isSyncModule_; - /** - * TODO(ramanpreet): - * Investigate an optimization that'll let us get rid of this NSMutableDictionary. - * Perhaps, have the code-generated TurboModule subclass implement - * getMethodArgConversionSelector below. - */ - NSMutableDictionary *methodArgConversionSelectors_; - NSDictionary *> *methodArgumentTypeNames_; + struct MethodInfo { + // Looked up on the method's first call, since it doesn't change. + NSMethodSignature *signature; + // Argument types declared with RCT_EXPORT_METHOD, used to convert arguments with RCTConvert. + NSArray *argumentTypeNames; + // RCTCxxConvert selectors that convert arguments to C++ structs, set by the generated code. + std::vector argConversionSelectors; + }; + struct MethodNameHash { + using is_transparent = void; + size_t operator()(std::string_view methodName) const + { + return std::hash{}(methodName); + } + }; + // Keyed by the method's JS name; looked up without allocating a std::string. + std::unordered_map> methodInfos_; + bool argumentTypeNamesLoaded_{false}; + std::mutex methodInfosMutex_; + MethodInfo &findOrCreateMethodInfo(std::string_view methodName); + NSMethodSignature *getMethodSignature(const char *methodName, SEL selector); bool isMethodSync(TurboModuleMethodValueKind returnType); bool mustCopyJSHeapArrayBufferBytes(TurboModuleMethodValueKind returnType); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index b8b5f2db3cd9..6008341c22e6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -658,9 +658,8 @@ TraceSection s( */ NSString *ObjCTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex) { - if (methodArgumentTypeNames_ == nullptr) { - NSMutableDictionary *> *methodArgumentTypeNames = [NSMutableDictionary new]; - + std::lock_guard lock(methodInfosMutex_); + if (!argumentTypeNamesLoaded_) { unsigned int numberOfMethods; Class cls = [instance_ class]; Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods); @@ -685,19 +684,21 @@ TraceSection s( [argumentTypes addObject:arguments[j].type]; } - NSString *normalizedOtherMethodName = [otherMethodName componentsSeparatedByString:@":"][0]; - methodArgumentTypeNames[normalizedOtherMethodName] = argumentTypes; + std::string_view otherMethodNameView = otherMethodName.UTF8String; + std::string_view normalizedOtherMethodName = otherMethodNameView.substr(0, otherMethodNameView.find(':')); + findOrCreateMethodInfo(normalizedOtherMethodName).argumentTypeNames = argumentTypes; } free(methods); } - methodArgumentTypeNames_ = methodArgumentTypeNames; + argumentTypeNamesLoaded_ = true; } - if (methodArgumentTypeNames_[methodName] != nullptr) { - assert([methodArgumentTypeNames_[methodName] count] > argIndex); - return methodArgumentTypeNames_[methodName][argIndex]; + auto it = methodInfos_.find(std::string_view(methodName.UTF8String)); + if (it != methodInfos_.end() && it->second.argumentTypeNames != nil) { + assert([it->second.argumentTypeNames count] > argIndex); + return it->second.argumentTypeNames[argIndex]; } return nil; @@ -820,6 +821,26 @@ TraceSection s( } } +ObjCTurboModule::MethodInfo &ObjCTurboModule::findOrCreateMethodInfo(std::string_view methodName) +{ + auto it = methodInfos_.find(methodName); + if (it == methodInfos_.end()) { + it = methodInfos_.emplace(std::string(methodName), MethodInfo{}).first; + } + return it->second; +} + +NSMethodSignature *ObjCTurboModule::getMethodSignature(const char *methodName, SEL selector) +{ + std::lock_guard lock(methodInfosMutex_); + MethodInfo &methodInfo = findOrCreateMethodInfo(methodName); + if (methodInfo.signature == nil) { + const NSObject *module = instance_; + methodInfo.signature = [module methodSignatureForSelector:selector]; + } + return methodInfo.signature; +} + NSInvocation *ObjCTurboModule::createMethodInvocation( jsi::Runtime &runtime, bool isSync, @@ -837,8 +858,7 @@ TraceSection s( TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName); } - const NSObject *module = instance_; - NSMethodSignature *methodSignature = [module methodSignatureForSelector:selector]; + NSMethodSignature *methodSignature = getMethodSignature(methodName, selector); if (count > methodSignature.numberOfArguments - 2) { throw jsi::JSError( runtime, @@ -964,36 +984,28 @@ TraceSection s( BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, size_t argIndex) { - return (methodArgConversionSelectors_ != nullptr) && (methodArgConversionSelectors_[methodName] != nullptr) && - ![methodArgConversionSelectors_[methodName][argIndex] isEqual:(id)kCFNull]; + return getMethodArgConversionSelector(methodName, argIndex) != nullptr; } SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, size_t argIndex) { - assert(hasMethodArgConversionSelector(methodName, argIndex)); - return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue; + std::lock_guard lock(methodInfosMutex_); + auto it = methodInfos_.find(std::string_view(methodName.UTF8String)); + if (it == methodInfos_.end() || argIndex >= it->second.argConversionSelectors.size()) { + return nullptr; + } + return it->second.argConversionSelectors[argIndex]; } void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName) { - if (methodArgConversionSelectors_ == nullptr) { - methodArgConversionSelectors_ = [NSMutableDictionary new]; - } - - if (methodArgConversionSelectors_[methodName] == nullptr) { - auto metaData = methodMap_.at([methodName UTF8String]); - auto argCount = metaData.argCount; - - methodArgConversionSelectors_[methodName] = [NSMutableArray arrayWithCapacity:argCount]; - for (int i = 0; i < argCount; i += 1) { - [methodArgConversionSelectors_[methodName] addObject:(id)kCFNull]; - } + const char *methodNameUTF8 = methodName.UTF8String; + std::lock_guard lock(methodInfosMutex_); + std::vector &selectors = findOrCreateMethodInfo(methodNameUTF8).argConversionSelectors; + if (selectors.empty()) { + selectors.resize(methodMap_.at(methodNameUTF8).argCount, nullptr); } - - SEL selector = NSSelectorFromString(fnName); - NSValue *selectorValue = [NSValue valueWithPointer:selector]; - - methodArgConversionSelectors_[methodName][argIndex] = selectorValue; + selectors.at(argIndex) = NSSelectorFromString(fnName); } void ObjCTurboModule::setEventEmitterCallback(EventEmitterCallback eventEmitterCallback)