diff --git a/ReactCommon/turbomodule/core/BUCK b/ReactCommon/turbomodule/core/BUCK index b45099674eb..d31e62e284e 100644 --- a/ReactCommon/turbomodule/core/BUCK +++ b/ReactCommon/turbomodule/core/BUCK @@ -5,11 +5,11 @@ APPLE_COMPILER_FLAGS = get_apple_compiler_flags() rn_xplat_cxx_library( name = "core", - srcs = glob(["**/*.cpp"]), + srcs = glob(["*.cpp"]), header_namespace = "", exported_headers = subdir_glob( [ - ("", "**/*.h"), + ("", "*.h"), ], prefix = "jsireact", ), @@ -22,6 +22,27 @@ rn_xplat_cxx_library( fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), force_static = True, + ios_deps = [ + "xplat//FBBaseLite:FBBaseLite", + "xplat//js/react-native-github:RCTCxxBridge", + "xplat//js/react-native-github:RCTCxxModule", + "xplat//js/react-native-github:ReactInternal", + ], + ios_exported_headers = subdir_glob( + [ + ("platform/ios", "*.h"), + ], + prefix = "jsireact", + ), + ios_frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + ios_srcs = glob( + [ + "platform/ios/**/*.cpp", + "platform/ios/**/*.mm", + ], + ), platforms = (ANDROID, APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h new file mode 100644 index 00000000000..3559540f028 --- /dev/null +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import + +#import +#import +#import +#import +#import + +namespace facebook { +namespace react { + +/** + * ObjC++ specific TurboModule base class. + */ +class JSI_EXPORT ObjCTurboModule : public TurboModule { +public: + ObjCTurboModule(const std::string &name, id instance, std::shared_ptr jsInvoker); + + virtual jsi::Value invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count) override; + + id instance_; +}; + +} // namespace react +} // namespace facebook + +// TODO: Consolidate this extension with the one in RCTSurfacePresenter. +@interface RCTBridge () + +- (std::shared_ptr)jsMessageThread; + +@end + +/** + * A backward-compatible protocol to be adopted by an existing RCTCxxModule-based class + * so that it can support the TurboModule system. + */ +@protocol RCTTurboCxxModule + +- (std::shared_ptr)getTurboModuleWithJsInvoker:(std::shared_ptr)jsInvoker; + +@end diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm new file mode 100644 index 00000000000..97fe2f78e65 --- /dev/null +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm @@ -0,0 +1,468 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTTurboModule.h" + +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +using namespace facebook; + +/** + * All static helper functions are ObjC++ specific. + */ +static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value) { + return jsi::Value((bool)[value boolValue]); +} + +static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value) { + return jsi::Value([value doubleValue]); +} + +static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value) { + return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: ""); +} + +static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value); +static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value) { + jsi::Object result = jsi::Object(runtime); + for (NSString *k in value) { + result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k])); + } + return result; +} + +static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value) { + jsi::Array result = jsi::Array(runtime, value.count); + for (size_t i = 0; i < value.count; i++) { + result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i])); + } + return result; +} + +static std::vector convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value) { + std::vector result; + for (size_t i = 0; i < value.count; i++) { + result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i])); + } + return result; +} + +static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value) { + if ([value isKindOfClass:[NSString class]]) { + return convertNSStringToJSIString(runtime, (NSString *)value); + } else if ([value isKindOfClass:[NSNumber class]]) { + if ([value isKindOfClass:[@YES class]]) { + return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value); + } + return convertNSNumberToJSINumber(runtime, (NSNumber *)value); + } else if ([value isKindOfClass:[NSDictionary class]]) { + return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value); + } else if ([value isKindOfClass:[NSArray class]]) { + return convertNSArrayToJSIArray(runtime, (NSArray *)value); + } else if (value == (id)kCFNull) { + return jsi::Value::null(); + } + return jsi::Value::undefined(); +} + +static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker); +static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value) { + return [NSString stringWithUTF8String:value.utf8(runtime).c_str()]; +} + +static NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr jsInvoker) { + size_t size = value.size(runtime); + NSMutableArray *result = [NSMutableArray new]; + for (size_t i = 0; i < size; i++) { + [result addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull]; + } + return [result copy]; +} + +static NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr jsInvoker) { + jsi::Array propertyNames = value.getPropertyNames(runtime); + size_t size = propertyNames.size(runtime); + NSMutableDictionary *result = [NSMutableDictionary new]; + for (size_t i = 0; i < size; i++) { + jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime); + NSString *k = convertJSIStringToNSString(runtime, name); + id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker) ?: (id)kCFNull; + result[k] = v; + } + return [result copy]; +} + +static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr jsInvoker); +static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker) { + if (value.isUndefined() || value.isNull()) { + return nil; + } + if (value.isBool()) { + return @(value.getBool()); + } + if (value.isNumber()) { + return @(value.getNumber()); + } + if (value.isString()) { + return convertJSIStringToNSString(runtime, value.getString(runtime)); + } + if (value.isObject()) { + jsi::Object o = value.getObject(runtime); + if (o.isArray(runtime)) { + return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker); + } + if (o.isFunction(runtime)) { + return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker); + } + return convertJSIObjectToNSDictionary(runtime, o, jsInvoker); + } + + throw std::runtime_error("Unsupported jsi::jsi::Value kind"); +} + +static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr jsInvoker) { + __block auto wrapper = std::make_shared(value.getFunction(runtime), runtime, jsInvoker); + return ^(NSArray *responses) { + if (wrapper == nullptr) { + throw std::runtime_error("callback arg cannot be called more than once"); + } + + std::shared_ptr rw = wrapper; + wrapper->jsInvoker->invokeAsync([rw, responses]() { + std::vector args = convertNSArrayToStdVector(rw->runtime, responses); + rw->callback.call(rw->runtime, (const jsi::Value *)args.data(), args.size()); + }); + + // The callback is single-use, so force release it here. + // Doing this also releases the jsi::jsi::Function early, since this block may not get released by ARC for a while, + // because the method invocation isn't guarded with @autoreleasepool. + wrapper = nullptr; + }; +} + +// Helper for creating Promise object. +struct PromiseWrapper : public react::LongLivedObject { + static std::shared_ptr create( + jsi::Function resolve, + jsi::Function reject, + jsi::Runtime &runtime, + std::shared_ptr jsInvoker) { + auto instance = std::make_shared(std::move(resolve), std::move(reject), runtime, jsInvoker); + // This instance needs to live longer than the caller's scope, since the resolve/reject functions may not + // be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the + // collection is cleared (e.g. when JS reloads). + react::LongLivedObjectCollection::get().add(instance); + return instance; + } + + PromiseWrapper( + jsi::Function resolve, + jsi::Function reject, + jsi::Runtime &runtime, + std::shared_ptr jsInvoker) + : resolveWrapper(std::make_shared(std::move(resolve), runtime, jsInvoker)), + rejectWrapper(std::make_shared(std::move(reject), runtime, jsInvoker)), + runtime(runtime), + jsInvoker(jsInvoker) {} + + RCTPromiseResolveBlock resolveBlock() { + return ^(id result) { + if (resolveWrapper == nullptr) { + throw std::runtime_error("Promise resolve arg cannot be called more than once"); + } + + // Retain the resolveWrapper so that it stays alive inside the lambda. + std::shared_ptr retainedWrapper = resolveWrapper; + jsInvoker->invokeAsync([retainedWrapper, result]() { + jsi::Runtime &rt = retainedWrapper->runtime; + jsi::Value arg = convertObjCObjectToJSIValue(rt, result); + retainedWrapper->callback.call(rt, arg); + }); + + // Prevent future invocation of the same resolve() function. + cleanup(); + }; + } + + RCTPromiseRejectBlock rejectBlock() { + return ^(NSString *code, NSString *message, NSError *error) { + // TODO: There is a chance `this` is no longer valid when this block executes. + if (rejectWrapper == nullptr) { + throw std::runtime_error("Promise reject arg cannot be called more than once"); + } + + // Retain the resolveWrapper so that it stays alive inside the lambda. + std::shared_ptr retainedWrapper = rejectWrapper; + NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error); + jsInvoker->invokeAsync([retainedWrapper, jsError]() { + jsi::Runtime &rt = retainedWrapper->runtime; + jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError); + retainedWrapper->callback.call(rt, arg); + }); + + // Prevent future invocation of the same resolve() function. + cleanup(); + }; + } + + void cleanup() { + resolveWrapper = nullptr; + rejectWrapper = nullptr; + allowRelease(); + } + + // CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after either + // the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need an extra + // mechanism to control that lifecycle. + std::shared_ptr resolveWrapper; + std::shared_ptr rejectWrapper; + jsi::Runtime &runtime; + std::shared_ptr jsInvoker; +}; + +using PromiseInvocationBlock = void (^)(jsi::Runtime& rt, std::shared_ptr wrapper); +static jsi::Value createPromise(jsi::Runtime &runtime, std::shared_ptr jsInvoker, PromiseInvocationBlock invoke) { + if (!invoke) { + return jsi::Value::undefined(); + } + + jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise"); + + // Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer. + // Otherwise, there's a risk of it getting released before the promise function below executes. + PromiseInvocationBlock invokeCopy = [invoke copy]; + jsi::Function fn = jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "fn"), + 2, + [invokeCopy, jsInvoker](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + if (count != 2) { + throw std::invalid_argument("Promise fn arg count must be 2"); + } + if (!invokeCopy) { + return jsi::Value::undefined(); + } + jsi::Function resolve = args[0].getObject(rt).getFunction(rt); + jsi::Function reject = args[1].getObject(rt).getFunction(rt); + auto wrapper = PromiseWrapper::create(std::move(resolve), std::move(reject), rt, jsInvoker); + invokeCopy(rt, wrapper); + return jsi::Value::undefined(); + }); + + return Promise.callAsConstructor(runtime, fn); +} + +namespace facebook { +namespace react { + +namespace { + +SEL resolveMethodSelector( + TurboModuleMethodValueKind valueKind, + id module, + std::string moduleName, + std::string methodName, + size_t argCount) { + // Assume the instance is properly bound to the right class at this point. + SEL selector = nil; + + // PromiseKind expects 2 additional function args for resolve() and reject() + size_t adjustedCount = valueKind == PromiseKind ? argCount + 2 : argCount; + NSString *baseMethodName = [NSString stringWithUTF8String:methodName.c_str()]; + + if (adjustedCount == 0) { + selector = NSSelectorFromString(baseMethodName); + if (![module respondsToSelector:selector]) { + throw std::runtime_error("Unable to find method: " + methodName + " for module: " + moduleName + ". Make sure the module is installed correctly."); + } + } else if (adjustedCount == 1) { + selector = NSSelectorFromString([NSString stringWithFormat:@"%@:", baseMethodName]); + if (![module respondsToSelector:selector]) { + throw std::runtime_error("Unable to find method: " + methodName + " for module: " + moduleName + ". Make sure the module is installed correctly."); + } + } else { + // TODO: This may be expensive lookup. The codegen output should specify the exact selector name. + unsigned int numberOfMethods; + Method *methods = class_copyMethodList([module class], &numberOfMethods); + if (methods) { + for (unsigned int i = 0; i < numberOfMethods; i++) { + SEL s = method_getName(methods[i]); + if ([NSStringFromSelector(s) hasPrefix:[NSString stringWithFormat:@"%@:", baseMethodName]]) { + selector = s; + break; + } + } + free(methods); + } + if (!selector) { + throw std::runtime_error("Unable to find method: " + methodName + " for module: " + moduleName + ". Make sure the module is installed correctly."); + } + } + + return selector; +} + +NSInvocation *getMethodInvocation( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const id module, + std::shared_ptr jsInvoker, + SEL selector, + const jsi::Value *args, + size_t count) { + NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]]; + [inv setSelector:selector]; + for (size_t i = 0; i < count; i++) { + const jsi::Value *arg = &args[i]; + if (arg->isBool()) { + bool v = arg->getBool(); + [inv setArgument:(void *)&v atIndex:i + 2]; + } else if (arg->isNumber()) { + double v = arg->getNumber(); + [inv setArgument:(void *)&v atIndex:i + 2]; + } else { + id v = convertJSIValueToObjCObject(runtime, *arg, jsInvoker); + [inv setArgument:(void *)&v atIndex:i + 2]; + } + } + [inv retainArguments]; + return inv; +} + +/** + * Perform method invocation on a specific queue as configured by the module class. + * This serves as a backward-compatible support for RCTBridgeModule's methodQueue API. + * + * In the future: + * - This methodQueue support may be removed for simplicity and consistency with Android. + * - ObjC module methods will be always be called from JS thread. + * They may decide to dispatch to a different queue as needed. + */ +void performMethodInvocation( + jsi::Runtime &runtime, + NSInvocation *inv, + TurboModuleMethodValueKind valueKind, + const id module, + std::shared_ptr jsInvoker, + jsi::Value *result) { + *result = jsi::Value::undefined(); + jsi::Runtime *rt = &runtime; + void (^block)() = ^{ + [inv invokeWithTarget:module]; + + if (valueKind == VoidKind) { + return; + } + + void *rawResult = NULL; + [inv getReturnValue:&rawResult]; + + // TODO: Re-use value conversion logic from existing impl, if possible. + switch (valueKind) { + case BooleanKind: + *result = convertNSNumberToJSIBoolean(*rt, (__bridge NSNumber *)rawResult); + break; + case NumberKind: + *result = convertNSNumberToJSINumber(*rt, (__bridge NSNumber *)rawResult); + break; + case StringKind: + *result = convertNSStringToJSIString(*rt, (__bridge NSString *)rawResult); + break; + case ObjectKind: + *result = convertNSDictionaryToJSIObject(*rt, (__bridge NSDictionary *)rawResult); + break; + case ArrayKind: + *result = convertNSArrayToJSIArray(*rt, (__bridge NSArray *)rawResult); + break; + case FunctionKind: + throw std::runtime_error("doInvokeTurboModuleMethod: FunctionKind is not supported yet."); + case PromiseKind: + throw std::runtime_error("doInvokeTurboModuleMethod: PromiseKind wasn't handled properly."); + case VoidKind: + throw std::runtime_error("doInvokeTurboModuleMethod: VoidKind wasn't handled properly."); + } + }; + + // Backward-compatibility layer for calling module methods on specific queue. + dispatch_queue_t methodQueue = NULL; + if ([module conformsToProtocol:@protocol(RCTBridgeModule)] && [module respondsToSelector:@selector(methodQueue)]) { + methodQueue = [module performSelector:@selector(methodQueue)]; + } + + if (methodQueue == NULL || methodQueue == RCTJSThread) { + // This is the default mode of execution: on JS thread. + block(); + } else if (methodQueue == dispatch_get_main_queue()) { + if (valueKind == VoidKind) { + // Void methods are treated as async for now, so there's no need to block here. + RCTExecuteOnMainQueue(block); + } else { + // This is not ideal, but provides the simplest mechanism for now. + // Eventually, methods should be responsible to queue things up to different queue if they need to. + // TODO: consider adding timer to warn if this method invocation takes too long. + RCTUnsafeExecuteOnMainQueueSync(block); + } + } else { + if (valueKind == VoidKind) { + dispatch_async(methodQueue, block); + } else { + dispatch_sync(methodQueue, block); + } + } +} + +} // namespace + +ObjCTurboModule::ObjCTurboModule( + const std::string &name, + id instance, + std::shared_ptr jsInvoker) + : TurboModule(name, jsInvoker), + instance_(instance) {} + +jsi::Value ObjCTurboModule::invokeMethod( + jsi::Runtime &runtime, + TurboModuleMethodValueKind valueKind, + const std::string &methodName, + const jsi::Value *args, + size_t count) { + SEL selector = resolveMethodSelector(valueKind, instance_, name_, methodName, count); + NSInvocation *inv = getMethodInvocation(runtime, valueKind, instance_, jsInvoker_, selector, args, count); + + if (valueKind == PromiseKind) { + // Promise return type is special cased today, i.e. it needs extra 2 function args for resolve() and reject(), to + // be passed to the actual ObjC++ class method. + return createPromise( + runtime, + jsInvoker_, + ^(jsi::Runtime &rt, std::shared_ptr wrapper) { + RCTPromiseResolveBlock resolveBlock = wrapper->resolveBlock(); + RCTPromiseRejectBlock rejectBlock = wrapper->rejectBlock(); + [inv setArgument:(void *)&resolveBlock atIndex:count + 2]; + [inv setArgument:(void *)&rejectBlock atIndex:count + 3]; + // The return type becomes void in the ObjC side. + jsi::Value result; + performMethodInvocation(rt, inv, VoidKind, instance_, jsInvoker_, &result); + }); + } + + jsi::Value result; + performMethodInvocation(runtime, inv, valueKind, instance_, jsInvoker_, &result); + return result; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h new file mode 100644 index 00000000000..5758a579c5d --- /dev/null +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTTurboModule.h" + +@protocol RCTTurboModuleManagerDelegate + +// TODO: Move to xplat codegen. +- (std::shared_ptr)getTurboModule:(const std::string &)name + instance:(id)instance + jsInvoker:(std::shared_ptr)jsInvoker; + +@optional + +/** + * Given a module name, return its actual class. If not provided, basic ObjC class lookup is performed. + */ +- (Class)getModuleClassFromName:(const char *)name; + +/** + * Given a module class, provide an instance for it. If not provided, default initializer is used. + */ +- (id)getModuleInstanceFromClass:(Class)moduleClass; + +/** + * Create an instance of a TurboModule without relying on any ObjC++ module instance. + */ +- (std::shared_ptr)getTurboModule:(const std::string &)name + jsInvoker:(std::shared_ptr)jsInvoker; + +@end + +@interface RCTTurboModuleManager : NSObject + +- (instancetype)initWithRuntime:(facebook::jsi::Runtime *)runtime + bridge:(RCTBridge *)bridge + delegate:(id)delegate; + +- (void)installJSBinding; + +- (std::shared_ptr)getModule:(const std::string &)name; + +@end diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm new file mode 100644 index 00000000000..46f69fb62c5 --- /dev/null +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -0,0 +1,148 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTTurboModuleManager.h" + +#import + +#import +#import +#import +#import +#import + +using namespace facebook; + +// Fallback lookup since RCT class prefix is sometimes stripped in the existing NativeModule system. +// This will be removed in the future. +static Class getFallbackClassFromName(const char *name) { + Class moduleClass = NSClassFromString([NSString stringWithUTF8String:name]); + if (!moduleClass) { + moduleClass = NSClassFromString([NSString stringWithFormat:@"RCT%s", name]); + } + return moduleClass; +} + +@implementation RCTTurboModuleManager +{ + jsi::Runtime *_runtime; + std::shared_ptr _jsInvoker; + std::shared_ptr _binding; + __weak id _delegate; + __weak RCTBridge *_bridge; +} + +- (instancetype)initWithRuntime:(jsi::Runtime *)runtime + bridge:(RCTBridge *)bridge + delegate:(id)delegate +{ + if (self = [super init]) { + _runtime = runtime; + _jsInvoker = std::make_shared(bridge.jsMessageThread); + _delegate = delegate; + _bridge = bridge; + + __weak __typeof(self) weakSelf = self; + auto moduleProvider = [weakSelf](const std::string &name) -> std::shared_ptr { + if (!weakSelf) { + return nullptr; + } + __strong __typeof(self) strongSelf = weakSelf; + + // Pure C++ modules get priority. + if ([strongSelf->_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) { + std::shared_ptr tm = [strongSelf->_delegate getTurboModule:name jsInvoker:strongSelf->_jsInvoker]; + if (tm != nullptr) { + return tm; + } + } + + Class moduleClass; + if ([strongSelf->_delegate respondsToSelector:@selector(getModuleClassFromName:)]) { + moduleClass = [strongSelf->_delegate getModuleClassFromName:name.c_str()]; + } else { + moduleClass = getFallbackClassFromName(name.c_str()); + } + assert(moduleClass); + + id module; + if ([strongSelf->_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) { + module = [strongSelf->_delegate getModuleInstanceFromClass:moduleClass]; + } else { + module = [moduleClass new]; + } + + /** + * It is reasonable for NativeModules to not want/need the bridge. + * In such cases, they won't have `@synthesize bridge = _bridge` in their + * implementation, and a `- (RCTBridge *) bridge { ... }` method won't be + * generated by the ObjC runtime. The property will also not be backed + * by an ivar, which makes writing to it unsafe. Therefore, we check if + * this method exists to know if we can safely set the bridge to the + * NativeModule. + */ + if ([module respondsToSelector:@selector(bridge)] && strongSelf->_bridge) { + /** + * Just because a NativeModule has the `bridge` method, it doesn't mean + * that it has synthesized the bridge in its implementation. Therefore, + * we need to surround the code that sets the bridge to the NativeModule + * inside a try/catch. This catches the cases where the NativeModule + * author specifies a `bridge` method manually. + */ + @try { + /** + * RCTBridgeModule declares the bridge property as readonly. + * Therefore, when authors of NativeModules synthesize the bridge + * via @synthesize bridge = bridge;, the ObjC runtime generates + * only a - (RCTBridge *) bridge: { ... } method. No setter is + * generated, so we have have to rely on the KVC API of ObjC to set + * the bridge property of these NativeModules. + */ + [(id)module setValue:strongSelf->_bridge forKey:@"bridge"]; + } + @catch (NSException *exception) { + RCTLogError(@"%@ has no setter or ivar for its bridge, which is not " + "permitted. You must either @synthesize the bridge property, " + "or provide your own setter method.", RCTBridgeModuleNameForClass(module)); + } + } + + // RCTCxxModule compatibility layer. + if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) { + if ([module respondsToSelector:@selector(getTurboModuleWithJsInvoker:)]) { + return [((id)module) getTurboModuleWithJsInvoker:strongSelf->_jsInvoker]; + } + + // Use TurboCxxModule compat class to wrap the CxxModule instance. + // This is only for migration convenience, despite less performant. + return std::make_shared([((RCTCxxModule *)module) createModule], strongSelf->_jsInvoker); + } + + return [strongSelf->_delegate getTurboModule:name instance:module jsInvoker:strongSelf->_jsInvoker]; + }; + + _binding = std::make_shared(moduleProvider); + } + return self; +} + +- (void)installJSBinding +{ + if (!_runtime) { + // jsi::Runtime doesn't exist when attached to Chrome debugger. + return; + } + + react::TurboModuleBinding::install(*_runtime, _binding); +} + +- (std::shared_ptr)getModule:(const std::string &)name +{ + return _binding->getModule(name); +} + +@end