Moved TurboModule iOS core to github

Summary:
This is the iOS binding for TurboModule.

To install the TurboModule binding:
* Provide `RCTCxxBridgeDelegate`
* Provide `RCTTurboModuleManagerDelegate`

Somewhere in `RCTCxxBridgeDelegate` impl:

```
RCTTurboModuleManager *manager = [[RCTTurboModuleManager alloc] initWithRuntime:&runtime bridge:bridge delegate:self];
[manager installJSBinding];
```

Doing so will install `global.__turboModuleProxy()` in JS space.

Note:
* The full instructions will be provided once all pieces are moved to OSS.
* Sample modules and binding setup will be provided later.

Reviewed By: RSNara

Differential Revision: D13583442

fbshipit-source-id: bb1cabd973e8a9ec59da6b145826e9ea234a96b3
This commit is contained in:
Kevin Gozali
2019-01-04 15:17:17 -08:00
committed by Facebook Github Bot
parent ac39795948
commit f2fccbb327
5 changed files with 742 additions and 2 deletions

View File

@@ -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\"",

View File

@@ -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 <memory>
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <cxxreact/MessageQueueThread.h>
#import <jsireact/JSCallInvoker.h>
#import <jsireact/TurboModule.h>
namespace facebook {
namespace react {
/**
* ObjC++ specific TurboModule base class.
*/
class JSI_EXPORT ObjCTurboModule : public TurboModule {
public:
ObjCTurboModule(const std::string &name, id<RCTTurboModule> instance, std::shared_ptr<JSCallInvoker> jsInvoker);
virtual jsi::Value invokeMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const jsi::Value *args,
size_t count) override;
id<RCTTurboModule> instance_;
};
} // namespace react
} // namespace facebook
// TODO: Consolidate this extension with the one in RCTSurfacePresenter.
@interface RCTBridge ()
- (std::shared_ptr<facebook::react::MessageQueueThread>)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 <RCTTurboModule>
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::JSCallInvoker>)jsInvoker;
@end

View File

@@ -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 <objc/runtime.h>
#import <sstream>
#import <vector>
#import <React/RCTBridgeModule.h>
#import <React/RCTUtils.h>
#import <jsireact/JSCallInvoker.h>
#import <jsireact/LongLivedObject.h>
#import <jsireact/TurboModule.h>
#import <jsireact/TurboModuleUtils.h>
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<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value) {
std::vector<jsi::Value> 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<react::JSCallInvoker> 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<react::JSCallInvoker> 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<react::JSCallInvoker> 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<react::JSCallInvoker> jsInvoker);
static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<react::JSCallInvoker> 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<react::JSCallInvoker> jsInvoker) {
__block auto wrapper = std::make_shared<react::CallbackWrapper>(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<react::CallbackWrapper> rw = wrapper;
wrapper->jsInvoker->invokeAsync([rw, responses]() {
std::vector<jsi::Value> 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<PromiseWrapper> create(
jsi::Function resolve,
jsi::Function reject,
jsi::Runtime &runtime,
std::shared_ptr<react::JSCallInvoker> jsInvoker) {
auto instance = std::make_shared<PromiseWrapper>(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<react::JSCallInvoker> jsInvoker)
: resolveWrapper(std::make_shared<react::CallbackWrapper>(std::move(resolve), runtime, jsInvoker)),
rejectWrapper(std::make_shared<react::CallbackWrapper>(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<react::CallbackWrapper> 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<react::CallbackWrapper> 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<react::CallbackWrapper> resolveWrapper;
std::shared_ptr<react::CallbackWrapper> rejectWrapper;
jsi::Runtime &runtime;
std::shared_ptr<react::JSCallInvoker> jsInvoker;
};
using PromiseInvocationBlock = void (^)(jsi::Runtime& rt, std::shared_ptr<PromiseWrapper> wrapper);
static jsi::Value createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> 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<RCTTurboModule> 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<RCTTurboModule> module,
std::shared_ptr<JSCallInvoker> 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<RCTTurboModule> module,
std::shared_ptr<JSCallInvoker> 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<RCTTurboModule> instance,
std::shared_ptr<JSCallInvoker> 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<PromiseWrapper> 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

View File

@@ -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 <NSObject>
// TODO: Move to xplat codegen.
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
instance:(id<RCTTurboModule>)instance
jsInvoker:(std::shared_ptr<facebook::react::JSCallInvoker>)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<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass;
/**
* Create an instance of a TurboModule without relying on any ObjC++ module instance.
*/
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::JSCallInvoker>)jsInvoker;
@end
@interface RCTTurboModuleManager : NSObject
- (instancetype)initWithRuntime:(facebook::jsi::Runtime *)runtime
bridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate;
- (void)installJSBinding;
- (std::shared_ptr<facebook::react::TurboModule>)getModule:(const std::string &)name;
@end

View File

@@ -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 <cassert>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxModule.h>
#import <React/RCTLog.h>
#import <jsireact/TurboCxxModule.h>
#import <jsireact/TurboModuleBinding.h>
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<facebook::react::JSCallInvoker> _jsInvoker;
std::shared_ptr<react::TurboModuleBinding> _binding;
__weak id<RCTTurboModuleManagerDelegate> _delegate;
__weak RCTBridge *_bridge;
}
- (instancetype)initWithRuntime:(jsi::Runtime *)runtime
bridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
{
if (self = [super init]) {
_runtime = runtime;
_jsInvoker = std::make_shared<react::JSCallInvoker>(bridge.jsMessageThread);
_delegate = delegate;
_bridge = bridge;
__weak __typeof(self) weakSelf = self;
auto moduleProvider = [weakSelf](const std::string &name) -> std::shared_ptr<react::TurboModule> {
if (!weakSelf) {
return nullptr;
}
__strong __typeof(self) strongSelf = weakSelf;
// Pure C++ modules get priority.
if ([strongSelf->_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
std::shared_ptr<react::TurboModule> 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<RCTTurboModule> 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<RCTTurboCxxModule>)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<react::TurboCxxModule>([((RCTCxxModule *)module) createModule], strongSelf->_jsInvoker);
}
return [strongSelf->_delegate getTurboModule:name instance:module jsInvoker:strongSelf->_jsInvoker];
};
_binding = std::make_shared<react::TurboModuleBinding>(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<facebook::react::TurboModule>)getModule:(const std::string &)name
{
return _binding->getModule(name);
}
@end