From c3c0c7cd1548eed20bb5f2b597a6f2df99596d1c Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Fri, 18 Jul 2025 16:06:56 +0100 Subject: [PATCH] [RN][iOS]Add basic support for TM in Swift - Codegen Disabled --- .../core/platform/ios/ReactCommon/RCTModule.h | 11 ++++ .../ios/ReactCommon/RCTSwiftTurboModule.h | 13 +++++ .../platform/ios/ReactCommon/RCTTurboModule.h | 14 +++++- .../ios/ReactCommon/RCTTurboModule.mm | 3 +- .../ios/ReactCommon/RCTTurboModuleManager.mm | 50 ++++++++++++------- ...eactAppDependencyProvider.podspec.template | 2 +- private/helloworld/scripts/metro.js | 2 +- 7 files changed, 71 insertions(+), 24 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTModule.h create mode 100644 packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTSwiftTurboModule.h diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTModule.h new file mode 100644 index 00000000000..19ba1cbf925 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTModule.h @@ -0,0 +1,11 @@ +// +// RCTModule.mm +// Pods +// +// Created by Riccardo Cipolleschi on 18/07/2025. +// + +@protocol RCTModule + +@end + diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTSwiftTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTSwiftTurboModule.h new file mode 100644 index 00000000000..51ae3ed4bd4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTSwiftTurboModule.h @@ -0,0 +1,13 @@ +// +// RCTSwiftTurboModule.h +// Pods +// +// Created by Riccardo Cipolleschi on 18/07/2025. +// + +#import "RCTModule.h" + +@protocol RCTSwiftTurboModule + +@end + 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 179ebf7d9dc..5de3ccd6ac4 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 @@ -19,8 +19,10 @@ #import #import +#import "RCTModule.h" + #define RCT_IS_TURBO_MODULE_CLASS(klass) \ - ((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)])) + ((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTModule)])) #define RCT_IS_TURBO_MODULE_INSTANCE(module) RCT_IS_TURBO_MODULE_CLASS([(module) class]) namespace facebook::react { @@ -186,13 +188,20 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { */ - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params; + +@optional +/** + * Return an instance of an Apple Module + */ +- (Class)getAppleModule; + @end /** * Protocol that objects can inherit to conform to be treated as turbomodules. * It inherits from RCTTurboModuleProvider, meaning that a TurboModule can create itself */ -@protocol RCTTurboModule +@protocol RCTTurboModule @optional - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper; @@ -210,3 +219,4 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { - (std::shared_ptr)decorateNativeMethodCallInvoker: (std::shared_ptr)nativeMethodCallInvoker; @end + 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 2a677974ca8..afae804a457 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 @@ -847,7 +847,7 @@ void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, size_ void ObjCTurboModule::setEventEmitterCallback(EventEmitterCallback eventEmitterCallback) { - if ([instance_ conformsToProtocol:@protocol(RCTTurboModule)] && + if ([instance_ conformsToProtocol:@protocol(RCTModule)] && [instance_ respondsToSelector:@selector(setEventEmitterCallback:)]) { EventEmitterCallbackWrapper *wrapper = [EventEmitterCallbackWrapper new]; wrapper->_eventEmitterCallback = std::move(eventEmitterCallback); @@ -859,3 +859,4 @@ void ObjCTurboModule::setEventEmitterCallback(EventEmitterCallback eventEmitterC @implementation EventEmitterCallbackWrapper @end + diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index 5168bbf415d..a9dade00ea8 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -159,7 +159,7 @@ class LegacyModuleNativeMethodCallInvoker : public ModuleNativeMethodCallInvoker bool isTurboModuleClass(Class cls) { - return [cls conformsToProtocol:@protocol(RCTTurboModule)]; + return [cls conformsToProtocol:@protocol(RCTModule)]; } bool isTurboModuleInstance(id module) @@ -339,18 +339,24 @@ typedef struct { /** * Step 2: Look for platform-specific modules. */ - id module = [self _moduleProviderForName:moduleName]; + id moduleProvider = [self _moduleProviderForName:moduleName]; TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName); // If we request that a TurboModule be created, its respective ObjC class must exist // If the class doesn't exist, then _provideObjCModule returns nil - if (!module) { + if (!moduleProvider) { return nullptr; } + + id module = nullptr; + if ([moduleProvider respondsToSelector:@selector(getAppleModule)]) { + module = (id)[self _provideObjCModule:moduleName moduleProvider:moduleProvider]; + } + id moduleOrProvider = module ? module : moduleProvider; std::shared_ptr nativeMethodCallInvoker = nullptr; - dispatch_queue_t methodQueue = (dispatch_queue_t)objc_getAssociatedObject(module, &kAssociatedMethodQueueKey); + dispatch_queue_t methodQueue = (dispatch_queue_t)objc_getAssociatedObject(moduleOrProvider, &kAssociatedMethodQueueKey); if (methodQueue) { /** * Step 2c: Create and native CallInvoker from the TurboModule's method queue. @@ -370,11 +376,11 @@ typedef struct { * Step 2d: If the moduleClass is a legacy CxxModule, return a TurboCxxModule instance that * wraps CxxModule. */ - Class moduleClass = [module class]; + Class moduleClass = [moduleOrProvider class]; if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) { // Use TurboCxxModule compat class to wrap the CxxModule instance. // This is only for migration convenience, despite less performant. - auto turboModule = std::make_shared([((RCTCxxModule *)module) createModule], _jsInvoker); + auto turboModule = std::make_shared([((RCTCxxModule *)moduleOrProvider) createModule], _jsInvoker); _turboModuleCache.insert({moduleName, turboModule}); return turboModule; } @@ -385,27 +391,27 @@ typedef struct { * Use respondsToSelector: below to infer conformance to @protocol(RCTTurboModule). Using conformsToProtocol: is * expensive. */ - if ([module respondsToSelector:@selector(getTurboModule:)]) { + if ([moduleProvider respondsToSelector:@selector(getTurboModule:)]) { ObjCTurboModule::InitParams params = { .moduleName = moduleName, - .instance = (id)module, + .instance = (id)moduleOrProvider, .jsInvoker = _jsInvoker, .nativeMethodCallInvoker = nativeMethodCallInvoker, .isSyncModule = methodQueue == RCTJSThread, .shouldVoidMethodsExecuteSync = (bool)RCTTurboModuleSyncVoidMethodsEnabled(), }; - auto turboModule = [(id)module getTurboModule:params]; + auto turboModule = [(id)moduleProvider getTurboModule:params]; if (turboModule == nullptr) { RCTLogError(@"TurboModule \"%@\"'s getTurboModule: method returned nil.", moduleClass); } _turboModuleCache.insert({moduleName, turboModule}); - if ([module respondsToSelector:@selector(installJSIBindingsWithRuntime:callInvoker:)]) { - [(id)module installJSIBindingsWithRuntime:*runtime callInvoker:_jsInvoker]; - } else if ([module respondsToSelector:@selector(installJSIBindingsWithRuntime:)]) { + if ([moduleOrProvider respondsToSelector:@selector(installJSIBindingsWithRuntime:callInvoker:)]) { + [(id)moduleOrProvider installJSIBindingsWithRuntime:*runtime callInvoker:_jsInvoker]; + } else if ([moduleOrProvider respondsToSelector:@selector(installJSIBindingsWithRuntime:)]) { // Old API without CallInvoker (deprecated) - [(id)module installJSIBindingsWithRuntime:*runtime]; + [(id)moduleOrProvider installJSIBindingsWithRuntime:*runtime]; } return turboModule; } @@ -496,16 +502,16 @@ typedef struct { if ([_delegate respondsToSelector:@selector(getModuleProvider:)]) { moduleProvider = [_delegate getModuleProvider:moduleName]; } - + if (RCTTurboModuleInteropEnabled() && ![self _isTurboModule:moduleName] && !moduleProvider) { return nil; } - + if (moduleProvider) { - if ([moduleProvider conformsToProtocol:@protocol(RCTTurboModule)]) { - // moduleProvider is also a TM, we need to initialize objectiveC properties, like the dispatch queue + if ([moduleProvider conformsToProtocol:@protocol(RCTModule)]) { return (id)[self _provideObjCModule:moduleName moduleProvider:moduleProvider]; } + // module is Cxx module return moduleProvider; } @@ -585,7 +591,12 @@ typedef struct { /** * Step 2a: Resolve platform-specific class. */ - Class moduleClass = moduleProvider ? [moduleProvider class] : [self _getModuleClassFromName:moduleName]; + Class moduleClass = moduleProvider ? + ([moduleProvider respondsToSelector:@selector(getAppleModule)] ? + [moduleProvider getAppleModule] : + [moduleProvider class]) : + [self _getModuleClassFromName:moduleName]; + __block id module = nil; @@ -640,7 +651,7 @@ typedef struct { return [moduleClass conformsToProtocol:@protocol(RCTBridgeModule)]; } - return [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]; + return [moduleClass conformsToProtocol:@protocol(RCTModule)]; } /** @@ -1104,3 +1115,4 @@ typedef struct { } @end + diff --git a/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template b/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template index 327bc9c102e..c977f45e579 100644 --- a/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template +++ b/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.author = "Meta Platforms, Inc. and its affiliates" s.platforms = min_supported_versions s.source = source - s.source_files = "**/RCTAppDependencyProvider.{h,mm}" + s.source_files = ["**/RCTAppDependencyProvider.{h,mm}", "**/*-Swift.{h,mm}"] # This guard prevent to install the dependencies when we run `pod install` in the old architecture. s.pod_target_xcconfig = { diff --git a/private/helloworld/scripts/metro.js b/private/helloworld/scripts/metro.js index b100f15ae21..3e24e27f551 100644 --- a/private/helloworld/scripts/metro.js +++ b/private/helloworld/scripts/metro.js @@ -23,7 +23,7 @@ program android: {}, }, root: path.join(__dirname, '../'), - reactNativePath: path.join(__dirname, '../../react-native'), + reactNativePath: path.join(__dirname, '../../../packages/react-native'), }, { experimentalDebugger: false,