This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
Checks: '>
clang-diagnostic-*,
'
...

View File

@ -0,0 +1,86 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "OBJC_ARC_PREPROCESSOR_FLAGS", "get_preprocessor_flags_for_build_mode", "get_static_library_ios_flags")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
rn_xplat_cxx_library(
name = "core",
srcs = glob(
["*.cpp"],
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "ReactCommon",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbandroid_deps = [
react_native_target("jni/react/jni:jni"),
],
fbandroid_exported_headers = subdir_glob(
[
("platform/android", "*.h"),
],
prefix = "ReactCommon",
),
fbandroid_srcs = glob(
[
"platform/android/**/*.cpp",
],
),
fbobjc_compiler_flags = [
"-Wall",
"-fobjc-arc-exceptions",
],
fbobjc_inherited_buck_flags = get_static_library_ios_flags(),
fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"],
fbobjc_preprocessor_flags = OBJC_ARC_PREPROCESSOR_FLAGS + get_preprocessor_flags_for_build_mode(),
force_static = True,
ios_deps = [
"//xplat/FBBaseLite:FBBaseLite",
"//xplat/js/react-native-github:RCTCxxModule",
"//xplat/js/react-native-github:ReactInternal",
],
ios_exported_headers = subdir_glob(
[
("platform/ios", "*.h"),
],
prefix = "ReactCommon",
),
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\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
"//xplat/fbsystrace:fbsystrace",
"//xplat/folly:headers_only",
"//xplat/folly:memory",
"//xplat/folly:molly",
"//xplat/jsi:JSIDynamic",
"//xplat/third-party/glog:glog",
react_native_xplat_target("cxxreact:bridge"),
react_native_xplat_target("cxxreact:module"),
react_native_xplat_target("callinvoker:callinvoker"),
],
exported_deps = [
"//xplat/jsi:jsi",
],
)

View File

@ -0,0 +1,52 @@
/*
* 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.
*/
#include "LongLivedObject.h"
namespace facebook {
namespace react {
// LongLivedObjectCollection
LongLivedObjectCollection &LongLivedObjectCollection::get() {
static LongLivedObjectCollection instance;
return instance;
}
LongLivedObjectCollection::LongLivedObjectCollection() {}
void LongLivedObjectCollection::add(std::shared_ptr<LongLivedObject> so) const {
std::lock_guard<std::mutex> lock(collectionMutex_);
collection_.insert(so);
}
void LongLivedObjectCollection::remove(const LongLivedObject *o) const {
std::lock_guard<std::mutex> lock(collectionMutex_);
auto p = collection_.begin();
for (; p != collection_.end(); p++) {
if (p->get() == o) {
break;
}
}
if (p != collection_.end()) {
collection_.erase(p);
}
}
void LongLivedObjectCollection::clear() const {
std::lock_guard<std::mutex> lock(collectionMutex_);
collection_.clear();
}
// LongLivedObject
LongLivedObject::LongLivedObject() {}
void LongLivedObject::allowRelease() {
LongLivedObjectCollection::get().remove(this);
}
} // namespace react
} // namespace facebook

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.
*/
#pragma once
#include <memory>
#include <mutex>
#include <unordered_set>
namespace facebook {
namespace react {
/**
* A simple wrapper class that can be registered to a collection that keep it
* alive for extended period of time. This object can be removed from the
* collection when needed.
*
* The subclass of this class must be created using std::make_shared<T>().
* After creation, add it to the `LongLivedObjectCollection`.
* When done with the object, call `allowRelease()` to allow the OS to release
* it.
*/
class LongLivedObject {
public:
void allowRelease();
protected:
LongLivedObject();
};
/**
* A singleton, thread-safe, write-only collection for the `LongLivedObject`s.
*/
class LongLivedObjectCollection {
public:
static LongLivedObjectCollection &get();
LongLivedObjectCollection(LongLivedObjectCollection const &) = delete;
void operator=(LongLivedObjectCollection const &) = delete;
void add(std::shared_ptr<LongLivedObject> o) const;
void remove(const LongLivedObject *o) const;
void clear() const;
private:
LongLivedObjectCollection();
mutable std::unordered_set<std::shared_ptr<LongLivedObject>> collection_;
mutable std::mutex collectionMutex_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,207 @@
/*
* 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.
*/
#include "TurboCxxModule.h"
#include <vector>
#include <ReactCommon/TurboModuleUtils.h>
#include <jsi/JSIDynamic.h>
using namespace facebook;
using namespace facebook::xplat::module;
namespace facebook {
namespace react {
namespace {
CxxModule::Callback makeTurboCxxModuleCallback(
jsi::Runtime &runtime,
std::weak_ptr<CallbackWrapper> weakWrapper) {
return [weakWrapper,
wrapperWasCalled = false](std::vector<folly::dynamic> args) mutable {
if (wrapperWasCalled) {
throw std::runtime_error("callback arg cannot be called more than once");
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync([weakWrapper, args]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> innerArgs;
for (auto &a : args) {
innerArgs.push_back(
jsi::valueFromDynamic(strongWrapper2->runtime(), a));
}
strongWrapper2->callback().call(
strongWrapper2->runtime(),
(const jsi::Value *)innerArgs.data(),
innerArgs.size());
strongWrapper2->destroy();
});
wrapperWasCalled = true;
};
}
} // namespace
TurboCxxModule::TurboCxxModule(
std::unique_ptr<CxxModule> cxxModule,
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(cxxModule->getName(), jsInvoker),
cxxMethods_(cxxModule->getMethods()),
cxxModule_(std::move(cxxModule)) {}
jsi::Value TurboCxxModule::get(
jsi::Runtime &runtime,
const jsi::PropNameID &propName) {
std::string propNameUtf8 = propName.utf8(runtime);
if (propNameUtf8 == "getConstants") {
// This is special cased because `getConstants()` is already a part of
// CxxModule.
return jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
jsi::Object result(rt);
auto constants = cxxModule_->getConstants();
for (auto &pair : constants) {
result.setProperty(
rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second));
}
return result;
});
}
for (auto &method : cxxMethods_) {
if (method.name == propNameUtf8) {
return jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this, propNameUtf8](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
return invokeMethod(rt, VoidKind, propNameUtf8, args, count);
});
}
}
return jsi::Value::undefined();
}
jsi::Value TurboCxxModule::invokeMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const jsi::Value *args,
size_t count) {
auto it = cxxMethods_.begin();
for (; it != cxxMethods_.end(); it++) {
auto method = *it;
if (method.name == methodName) {
break;
}
}
if (it == cxxMethods_.end()) {
throw std::runtime_error(
"Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
}
auto method = *it;
if (method.syncFunc) {
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
return jsi::valueFromDynamic(
runtime, method.syncFunc(std::move(innerArgs)));
} else if (method.func && !method.isPromise) {
// Async method.
CxxModule::Callback first;
CxxModule::Callback second;
if (count < method.callbacks) {
throw std::invalid_argument(folly::to<std::string>(
"Expected ",
method.callbacks,
" callbacks, but only ",
count,
" parameters provided"));
}
if (method.callbacks == 1) {
auto wrapper = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(runtime, wrapper);
} else if (method.callbacks == 2) {
auto wrapper1 = CallbackWrapper::createWeak(
args[count - 2].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
auto wrapper2 = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(runtime, wrapper1);
second = makeTurboCxxModuleCallback(runtime, wrapper2);
}
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count - method.callbacks; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
method.func(std::move(innerArgs), first, second);
} else if (method.isPromise) {
return createPromiseAsJSIValue(
runtime,
[method, args, count, this](
jsi::Runtime &rt, std::shared_ptr<Promise> promise) {
auto resolveWrapper = CallbackWrapper::createWeak(
promise->resolve_.getFunction(rt), rt, jsInvoker_);
auto rejectWrapper = CallbackWrapper::createWeak(
promise->reject_.getFunction(rt), rt, jsInvoker_);
CxxModule::Callback resolve =
makeTurboCxxModuleCallback(rt, resolveWrapper);
CxxModule::Callback reject =
makeTurboCxxModuleCallback(rt, rejectWrapper);
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(rt, args[i]));
}
method.func(std::move(innerArgs), resolve, reject);
});
}
return jsi::Value::undefined();
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <vector>
#include <cxxreact/CxxModule.h>
#include "TurboModule.h"
namespace facebook {
namespace react {
/**
* A helper class to convert the legacy CxxModule instance to a TurboModule
* instance. This should be used only for migration purpose (to TurboModule),
* since it's not very performant due to a lot of back-and-forth value
* conversions between folly::dynamic and jsi::Value.
*/
class JSI_EXPORT TurboCxxModule : public TurboModule {
public:
TurboCxxModule(
std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule,
std::shared_ptr<CallInvoker> jsInvoker);
virtual facebook::jsi::Value get(
facebook::jsi::Runtime &runtime,
const facebook::jsi::PropNameID &propName) override;
jsi::Value invokeMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const jsi::Value *args,
size_t count);
private:
std::vector<facebook::xplat::module::CxxModule::Method> cxxMethods_;
std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,44 @@
/*
* 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.
*/
#include "TurboModule.h"
using namespace facebook;
namespace facebook {
namespace react {
TurboModule::TurboModule(
const std::string &name,
std::shared_ptr<CallInvoker> jsInvoker)
: name_(name), jsInvoker_(jsInvoker) {}
TurboModule::~TurboModule() {}
jsi::Value TurboModule::get(
jsi::Runtime &runtime,
const jsi::PropNameID &propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return jsi::Value::undefined();
}
MethodMetadata meta = p->second;
return jsi::Function::createFromHostFunction(
runtime,
propName,
meta.argCount,
[this, meta](
facebook::jsi::Runtime &rt,
const facebook::jsi::Value &thisVal,
const facebook::jsi::Value *args,
size_t count) { return meta.invoker(rt, *this, args, count); });
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,71 @@
/*
* 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.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
namespace facebook {
namespace react {
/**
* For now, support the same set of return types as existing impl.
* This can be improved to support richer typed objects.
*/
enum TurboModuleMethodValueKind {
VoidKind,
BooleanKind,
NumberKind,
StringKind,
ObjectKind,
ArrayKind,
FunctionKind,
PromiseKind,
};
/**
* Base HostObject class for every module to be exposed to JS
*/
class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
public:
TurboModule(const std::string &name, std::shared_ptr<CallInvoker> jsInvoker);
virtual ~TurboModule();
virtual facebook::jsi::Value get(
facebook::jsi::Runtime &runtime,
const facebook::jsi::PropNameID &propName) override;
const std::string name_;
std::shared_ptr<CallInvoker> jsInvoker_;
protected:
struct MethodMetadata {
size_t argCount;
facebook::jsi::Value (*invoker)(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count);
};
std::unordered_map<std::string, MethodMetadata> methodMap_;
};
/**
* An app/platform-specific provider function to get an instance of a module
* given a name.
*/
using TurboModuleProviderFunctionType =
std::function<std::shared_ptr<TurboModule>(const std::string &name)>;
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,82 @@
/*
* 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.
*/
#include "TurboModuleBinding.h"
#include <stdexcept>
#include <string>
#include <ReactCommon/LongLivedObject.h>
#include <cxxreact/SystraceSection.h>
using namespace facebook;
namespace facebook {
namespace react {
/**
* Public API to install the TurboModule system.
*/
TurboModuleBinding::TurboModuleBinding(
const TurboModuleProviderFunctionType &&moduleProvider)
: moduleProvider_(std::move(moduleProvider)) {}
void TurboModuleBinding::install(
jsi::Runtime &runtime,
const TurboModuleProviderFunctionType &&moduleProvider) {
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
[binding =
std::make_shared<TurboModuleBinding>(std::move(moduleProvider))](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
return binding->jsProxy(rt, thisVal, args, count);
}));
}
TurboModuleBinding::~TurboModuleBinding() {
LongLivedObjectCollection::get().clear();
}
std::shared_ptr<TurboModule> TurboModuleBinding::getModule(
const std::string &name) {
std::shared_ptr<TurboModule> module = nullptr;
{
SystraceSection s("TurboModuleBinding::getModule", "module", name);
module = moduleProvider_(name);
}
return module;
}
jsi::Value TurboModuleBinding::jsProxy(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
if (count != 1) {
throw std::invalid_argument(
"TurboModuleBinding::jsProxy arg count must be 1");
}
std::string moduleName = args[0].getString(runtime).utf8(runtime);
std::shared_ptr<TurboModule> module = getModule(moduleName);
if (module == nullptr) {
return jsi::Value::null();
}
return jsi::Object::createFromHostObject(runtime, std::move(module));
}
} // namespace react
} // namespace facebook

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.
*/
#pragma once
#include <string>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class JSCallInvoker;
/**
* Represents the JavaScript binding for the TurboModule system.
*/
class TurboModuleBinding {
public:
/*
* Installs TurboModuleBinding into JavaScript runtime.
* Thread synchronization must be enforced externally.
*/
static void install(
jsi::Runtime &runtime,
const TurboModuleProviderFunctionType &&moduleProvider);
TurboModuleBinding(const TurboModuleProviderFunctionType &&moduleProvider);
virtual ~TurboModuleBinding();
/**
* Get an TurboModule instance for the given module name.
*/
std::shared_ptr<TurboModule> getModule(const std::string &name);
private:
/**
* A lookup function exposed to JS to get an instance of a TurboModule
* for the given name.
*/
jsi::Value jsProxy(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count);
TurboModuleProviderFunctionType moduleProvider_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,105 @@
/*
* 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.
*/
#include "TurboModuleUtils.h"
namespace facebook {
namespace react {
static jsi::Value deepCopyJSIValue(jsi::Runtime &rt, const jsi::Value &value) {
if (value.isNull()) {
return jsi::Value::null();
}
if (value.isBool()) {
return jsi::Value(value.getBool());
}
if (value.isNumber()) {
return jsi::Value(value.getNumber());
}
if (value.isString()) {
return value.getString(rt);
}
if (value.isObject()) {
jsi::Object o = value.getObject(rt);
if (o.isArray(rt)) {
return deepCopyJSIArray(rt, o.getArray(rt));
}
if (o.isFunction(rt)) {
return o.getFunction(rt);
}
return deepCopyJSIObject(rt, o);
}
return jsi::Value::undefined();
}
jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj) {
jsi::Object copy(rt);
jsi::Array propertyNames = obj.getPropertyNames(rt);
size_t size = propertyNames.size(rt);
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(rt, i).getString(rt);
jsi::Value value = obj.getProperty(rt, name);
copy.setProperty(rt, name, deepCopyJSIValue(rt, value));
}
return copy;
}
jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr) {
size_t size = arr.size(rt);
jsi::Array copy(rt, size);
for (size_t i = 0; i < size; i++) {
copy.setValueAtIndex(
rt, i, deepCopyJSIValue(rt, arr.getValueAtIndex(rt, i)));
}
return copy;
}
Promise::Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject)
: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {}
void Promise::resolve(const jsi::Value &result) {
resolve_.call(runtime_, result);
}
void Promise::reject(const std::string &message) {
jsi::Object error(runtime_);
error.setProperty(
runtime_, "message", jsi::String::createFromUtf8(runtime_, message));
reject_.call(runtime_, error);
}
jsi::Value createPromiseAsJSIValue(
jsi::Runtime &rt,
const PromiseSetupFunctionType func) {
jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise");
jsi::Function fn = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "fn"),
2,
[func](
jsi::Runtime &rt2,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2);
jsi::Function reject = args[1].getObject(rt2).getFunction(rt2);
auto wrapper = std::make_shared<Promise>(
rt2, std::move(resolve), std::move(reject));
func(rt2, wrapper);
return jsi::Value::undefined();
});
return JSPromise.callAsConstructor(rt, fn);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,89 @@
/*
* 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.
*/
#pragma once
#include <cassert>
#include <string>
#include <folly/Optional.h>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/LongLivedObject.h>
using namespace facebook;
namespace facebook {
namespace react {
jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj);
jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr);
struct Promise : public LongLivedObject {
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
void resolve(const jsi::Value &result);
void reject(const std::string &error);
jsi::Runtime &runtime_;
jsi::Function resolve_;
jsi::Function reject_;
};
using PromiseSetupFunctionType =
std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
jsi::Value createPromiseAsJSIValue(
jsi::Runtime &rt,
const PromiseSetupFunctionType func);
// Helper for passing jsi::Function arg to other methods.
class CallbackWrapper : public LongLivedObject {
private:
CallbackWrapper(
jsi::Function &&callback,
jsi::Runtime &runtime,
std::shared_ptr<CallInvoker> jsInvoker)
: callback_(std::move(callback)),
runtime_(runtime),
jsInvoker_(std::move(jsInvoker)) {}
jsi::Function callback_;
jsi::Runtime &runtime_;
std::shared_ptr<CallInvoker> jsInvoker_;
public:
static std::weak_ptr<CallbackWrapper> createWeak(
jsi::Function &&callback,
jsi::Runtime &runtime,
std::shared_ptr<CallInvoker> jsInvoker) {
auto wrapper = std::shared_ptr<CallbackWrapper>(
new CallbackWrapper(std::move(callback), runtime, jsInvoker));
LongLivedObjectCollection::get().add(wrapper);
return wrapper;
}
// Delete the enclosed jsi::Function
void destroy() {
allowRelease();
}
jsi::Function &callback() {
return callback_;
}
jsi::Runtime &runtime() {
return runtime_;
}
CallInvoker &jsInvoker() {
return *(jsInvoker_);
}
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,647 @@
/*
* 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.
*/
#include <memory>
#include <sstream>
#include <string>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/JSIDynamic.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
#include "JavaTurboModule.h"
namespace facebook {
namespace react {
JavaTurboModule::JavaTurboModule(
const std::string &name,
jni::alias_ref<JTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker)
: TurboModule(name, jsInvoker),
instance_(jni::make_global(instance)),
nativeInvoker_(nativeInvoker) {}
namespace {
jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
jsi::Function &&function,
jsi::Runtime &rt,
std::shared_ptr<CallInvoker> jsInvoker) {
auto weakWrapper =
react::CallbackWrapper::createWeak(std::move(function), rt, jsInvoker);
std::function<void(folly::dynamic)> fn =
[weakWrapper,
wrapperWasCalled = false](folly::dynamic responses) mutable {
if (wrapperWasCalled) {
throw std::runtime_error(
"callback 2 arg cannot be called more than once");
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
// TODO (T43155926) valueFromDynamic already returns a Value array.
// Don't iterate again
jsi::Value args =
jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
auto argsArray = args.getObject(strongWrapper2->runtime())
.asArray(strongWrapper2->runtime());
std::vector<jsi::Value> result;
for (size_t i = 0; i < argsArray.size(strongWrapper2->runtime());
i++) {
result.emplace_back(
strongWrapper2->runtime(),
argsArray.getValueAtIndex(strongWrapper2->runtime(), i));
}
strongWrapper2->callback().call(
strongWrapper2->runtime(),
(const jsi::Value *)result.data(),
result.size());
strongWrapper2->destroy();
});
wrapperWasCalled = true;
};
return JCxxCallbackImpl::newObjectCxxArgs(fn);
}
template <typename T>
std::string to_string(T v) {
std::ostringstream stream;
stream << v;
return stream.str();
}
// This is used for generating short exception strings.
std::string stringifyJSIValue(const jsi::Value &v, jsi::Runtime *rt = nullptr) {
if (v.isUndefined()) {
return "undefined";
}
if (v.isNull()) {
return "null";
}
if (v.isBool()) {
return std::string("a boolean (") + (v.getBool() ? "true" : "false") + ")";
}
if (v.isNumber()) {
return "a number (" + to_string(v.getNumber()) + ")";
}
if (v.isString()) {
return "a string (\"" + v.getString(*rt).utf8(*rt) + "\")";
}
assert(v.isObject() && "Expecting object.");
return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function"
: "an object";
}
class JavaTurboModuleArgumentConversionException : public std::runtime_error {
public:
JavaTurboModuleArgumentConversionException(
const std::string &expectedType,
int index,
const std::string &methodName,
const jsi::Value *arg,
jsi::Runtime *rt)
: std::runtime_error(
"Expected argument " + to_string(index) + " of method \"" +
methodName + "\" to be a " + expectedType + ", but got " +
stringifyJSIValue(*arg, rt)) {}
};
class JavaTurboModuleInvalidArgumentTypeException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentTypeException(
const std::string &actualType,
int argIndex,
const std::string &methodName)
: std::runtime_error(
"Called method \"" + methodName + "\" with unsupported type " +
actualType + " at argument " + to_string(argIndex)) {}
};
class JavaTurboModuleInvalidArgumentCountException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentCountException(
const std::string &methodName,
int actualArgCount,
int expectedArgCount)
: std::runtime_error(
"TurboModule method \"" + methodName + "\" called with " +
to_string(actualArgCount) +
" arguments (expected argument count: " +
to_string(expectedArgCount) + ").") {}
};
/**
* See
* https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
* for a description of Java method signature structure.
*/
std::vector<std::string> getMethodArgTypesFromSignature(
const std::string &methodSignature) {
std::vector<std::string> methodArgs;
for (auto it = methodSignature.begin(); it != methodSignature.end();
it += 1) {
if (*it == '(') {
continue;
}
if (*it == ')') {
break;
}
std::string type;
if (*it == '[') {
type += *it;
it += 1;
}
if (*it == 'L') {
for (; it != methodSignature.end(); it += 1) {
type += *it;
if (*it == ';') {
break;
}
}
} else {
type += *it;
}
methodArgs.push_back(type);
}
return methodArgs;
}
} // namespace
// fnjni already does this conversion, but since we are using plain JNI, this
// needs to be done again
// TODO (axe) Reuse existing implementation as needed - the exist in
// MethodInvoker.cpp
JNIArgs JavaTurboModule::convertJSIArgsToJNIArgs(
JNIEnv *env,
jsi::Runtime &rt,
std::string methodName,
std::vector<std::string> methodArgTypes,
const jsi::Value *args,
size_t count,
std::shared_ptr<CallInvoker> jsInvoker,
TurboModuleMethodValueKind valueKind) {
unsigned int expectedArgumentCount = valueKind == PromiseKind
? methodArgTypes.size() - 1
: methodArgTypes.size();
if (expectedArgumentCount != count) {
throw JavaTurboModuleInvalidArgumentCountException(
methodName, count, expectedArgumentCount);
}
JNIArgs jniArgs(valueKind == PromiseKind ? count + 1 : count);
auto &jargs = jniArgs.args_;
auto &globalRefs = jniArgs.globalRefs_;
auto makeGlobalIfNecessary =
[&globalRefs, env, valueKind](jobject obj) -> jobject {
if (valueKind == VoidKind) {
jobject globalObj = env->NewGlobalRef(obj);
globalRefs.push_back(globalObj);
env->DeleteLocalRef(obj);
return globalObj;
}
return obj;
};
jclass booleanClass = nullptr;
jclass doubleClass = nullptr;
for (unsigned int argIndex = 0; argIndex < count; argIndex += 1) {
std::string type = methodArgTypes.at(argIndex);
const jsi::Value *arg = &args[argIndex];
jvalue *jarg = &jargs[argIndex];
if (type == "D") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->d = arg->getNumber();
continue;
}
if (type == "Z") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
jarg->z = (jboolean)arg->getBool();
continue;
}
if (!(type == "Ljava/lang/Double;" || type == "Ljava/lang/Boolean;" ||
type == "Ljava/lang/String;" ||
type == "Lcom/facebook/react/bridge/ReadableArray;" ||
type == "Lcom/facebook/react/bridge/Callback;" ||
type == "Lcom/facebook/react/bridge/ReadableMap;")) {
throw JavaTurboModuleInvalidArgumentTypeException(
type, argIndex, methodName);
}
if (arg->isNull() || arg->isUndefined()) {
jarg->l = nullptr;
continue;
}
if (type == "Ljava/lang/Double;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
if (doubleClass == nullptr) {
doubleClass = env->FindClass("java/lang/Double");
}
jmethodID doubleConstructor =
env->GetMethodID(doubleClass, "<init>", "(D)V");
jarg->l = makeGlobalIfNecessary(
env->NewObject(doubleClass, doubleConstructor, arg->getNumber()));
continue;
}
if (type == "Ljava/lang/Boolean;") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
if (booleanClass == nullptr) {
booleanClass = env->FindClass("java/lang/Boolean");
}
jmethodID booleanConstructor =
env->GetMethodID(booleanClass, "<init>", "(Z)V");
jarg->l = makeGlobalIfNecessary(
env->NewObject(booleanClass, booleanConstructor, arg->getBool()));
continue;
}
if (type == "Ljava/lang/String;") {
if (!arg->isString()) {
throw JavaTurboModuleArgumentConversionException(
"string", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()));
continue;
}
if (type == "Lcom/facebook/react/bridge/ReadableArray;") {
if (!(arg->isObject() && arg->getObject(rt).isArray(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Array", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeArray::newObjectCxxArgs(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
continue;
}
if (type == "Lcom/facebook/react/bridge/Callback;") {
if (!(arg->isObject() && arg->getObject(rt).isFunction(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Function", argIndex, methodName, arg, &rt);
}
jsi::Function fn = arg->getObject(rt).getFunction(rt);
jarg->l = makeGlobalIfNecessary(
createJavaCallbackFromJSIFunction(std::move(fn), rt, jsInvoker)
.release());
continue;
}
if (type == "Lcom/facebook/react/bridge/ReadableMap;") {
if (!(arg->isObject())) {
throw JavaTurboModuleArgumentConversionException(
"Object", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
continue;
}
}
return jniArgs;
}
jsi::Value convertFromJMapToValue(JNIEnv *env, jsi::Runtime &rt, jobject arg) {
// We currently use Java Argument.makeNativeMap() method to do this conversion
// This could also be done purely in C++, but iterative over map methods
// but those may end up calling reflection methods anyway
// TODO (axe) Investigate the best way to convert Java Map to Value
jclass jArguments = env->FindClass("com/facebook/react/bridge/Arguments");
static jmethodID jMakeNativeMap = env->GetStaticMethodID(
jArguments,
"makeNativeMap",
"(Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;");
auto constants =
(jobject)env->CallStaticObjectMethod(jArguments, jMakeNativeMap, arg);
auto jResult = jni::adopt_local(constants);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
jsi::Value JavaTurboModule::invokeJavaMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const std::string &methodSignature,
const jsi::Value *args,
size_t argCount) {
JNIEnv *env = jni::Environment::current();
auto instance = instance_.get();
/**
* To account for jclasses and other misc LocalReferences we create.
*/
unsigned int buffer = 6;
/**
* For promises, we have to create a resolve fn, a reject fn, and a promise
* object. For normal returns, we just create the return object.
*/
unsigned int maxReturnObjects = 3;
/**
* When the return type is void, all JNI LocalReferences are converted to
* GlobalReferences. The LocalReferences are then promptly deleted
* after the conversion.
*/
unsigned int actualArgCount = valueKind == VoidKind ? 0 : argCount;
unsigned int estimatedLocalRefCount =
actualArgCount + maxReturnObjects + buffer;
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for invokeJavaMethod is popped,
* all LocalReferences are deleted.
*
* In total, there can be at most kJniLocalRefMax (= 512) Jni
* LocalReferences alive at a time. estimatedLocalRefCount is provided
* so that PushLocalFrame can throw an out of memory error when the total
* number of alive LocalReferences is estimatedLocalRefCount smaller than
* kJniLocalRefMax.
*/
jni::JniLocalScope scope(env, estimatedLocalRefCount);
jclass cls = env->GetObjectClass(instance);
jmethodID methodID =
env->GetMethodID(cls, methodName.c_str(), methodSignature.c_str());
// If the method signature doesn't match, show a redbox here instead of
// crashing later.
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
// TODO(T43933641): Refactor to remove this special-casing
if (methodName == "getConstants") {
auto constantsMap = (jobject)env->CallObjectMethod(instance, methodID);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (constantsMap == nullptr) {
return jsi::Value::undefined();
}
return convertFromJMapToValue(env, runtime, constantsMap);
}
std::vector<std::string> methodArgTypes =
getMethodArgTypesFromSignature(methodSignature);
JNIArgs jniArgs = convertJSIArgsToJNIArgs(
env,
runtime,
methodName,
methodArgTypes,
args,
argCount,
jsInvoker_,
valueKind);
auto &jargs = jniArgs.args_;
auto &globalRefs = jniArgs.globalRefs_;
switch (valueKind) {
case VoidKind: {
nativeInvoker_->invokeAsync(
[jargs, globalRefs, methodID, instance_ = instance_]() mutable
-> void {
/**
* TODO(ramanpreet): Why do we have to require the environment
* again? Why does JNI crash when we use the env from the upper
* scope?
*/
JNIEnv *env = jni::Environment::current();
env->CallVoidMethodA(instance_.get(), methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
for (auto globalRef : globalRefs) {
env->DeleteGlobalRef(globalRef);
}
});
return jsi::Value::undefined();
}
case BooleanKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Boolean;") {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (returnObject == nullptr) {
return jsi::Value::null();
}
jclass booleanClass = env->FindClass("java/lang/Boolean");
jmethodID booleanValueMethod =
env->GetMethodID(booleanClass, "booleanValue", "()Z");
bool returnBoolean =
(bool)env->CallBooleanMethod(returnObject, booleanValueMethod);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return jsi::Value(returnBoolean);
}
bool returnBoolean =
(bool)env->CallBooleanMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return jsi::Value(returnBoolean);
}
case NumberKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Double;") {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (returnObject == nullptr) {
return jsi::Value::null();
}
jclass doubleClass = env->FindClass("java/lang/Double");
jmethodID doubleValueMethod =
env->GetMethodID(doubleClass, "doubleValue", "()D");
double returnDouble =
(double)env->CallDoubleMethod(returnObject, doubleValueMethod);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return jsi::Value(returnDouble);
}
double returnDouble =
(double)env->CallDoubleMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return jsi::Value(returnDouble);
}
case StringKind: {
auto returnString =
(jstring)env->CallObjectMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (returnString == nullptr) {
return jsi::Value::null();
}
const char *js = env->GetStringUTFChars(returnString, nullptr);
std::string result = js;
env->ReleaseStringUTFChars(returnString, js);
return jsi::Value(runtime, jsi::String::createFromUtf8(runtime, result));
}
case ObjectKind: {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (returnObject == nullptr) {
return jsi::Value::null();
}
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
case ArrayKind: {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (returnObject == nullptr) {
return jsi::Value::null();
}
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeArray::jhybridobject>(jResult);
return jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
case PromiseKind: {
jsi::Function Promise =
runtime.global().getPropertyAsFunction(runtime, "Promise");
jsi::Function promiseConstructorArg = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "fn"),
2,
[this, &jargs, argCount, instance, methodID, env](
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *promiseConstructorArgs,
size_t promiseConstructorArgCount) {
if (promiseConstructorArgCount != 2) {
throw std::invalid_argument("Promise fn arg count must be 2");
}
jsi::Function resolveJSIFn =
promiseConstructorArgs[0].getObject(runtime).getFunction(
runtime);
jsi::Function rejectJSIFn =
promiseConstructorArgs[1].getObject(runtime).getFunction(
runtime);
auto resolve = createJavaCallbackFromJSIFunction(
std::move(resolveJSIFn), runtime, jsInvoker_)
.release();
auto reject = createJavaCallbackFromJSIFunction(
std::move(rejectJSIFn), runtime, jsInvoker_)
.release();
jclass jPromiseImpl =
env->FindClass("com/facebook/react/bridge/PromiseImpl");
jmethodID jPromiseImplConstructor = env->GetMethodID(
jPromiseImpl,
"<init>",
"(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V");
jobject promise = env->NewObject(
jPromiseImpl, jPromiseImplConstructor, resolve, reject);
jargs[argCount].l = promise;
env->CallVoidMethodA(instance, methodID, jargs.data());
return jsi::Value::undefined();
});
jsi::Value promise =
Promise.callAsConstructor(runtime, promiseConstructorArg);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return promise;
}
default:
throw std::runtime_error(
"Unable to find method module: " + methodName + "(" +
methodSignature + ")");
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,64 @@
/*
* 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.
*/
#pragma once
#include <string>
#include <unordered_set>
#include <ReactCommon/TurboModule.h>
#include <ReactCommon/TurboModuleUtils.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/JCallback.h>
namespace facebook {
namespace react {
struct JNIArgs {
JNIArgs(size_t count) : args_(count) {}
std::vector<jvalue> args_;
std::vector<jobject> globalRefs_;
};
struct JTurboModule : jni::JavaClass<JTurboModule> {
static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/turbomodule/core/interfaces/TurboModule;";
};
class JSI_EXPORT JavaTurboModule : public TurboModule {
public:
JavaTurboModule(
const std::string &name,
jni::alias_ref<JTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker);
jsi::Value invokeJavaMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const std::string &methodSignature,
const jsi::Value *args,
size_t argCount);
private:
jni::global_ref<JTurboModule> instance_;
std::shared_ptr<CallInvoker> nativeInvoker_;
JNIArgs convertJSIArgsToJNIArgs(
JNIEnv *env,
jsi::Runtime &rt,
std::string methodName,
std::vector<std::string> methodArgTypes,
const jsi::Value *args,
size_t count,
std::shared_ptr<CallInvoker> jsInvoker,
TurboModuleMethodValueKind valueKind);
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,229 @@
/*
* 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 <React/RCTModuleMethod.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <ReactCommon/TurboModuleUtils.h>
#import <string>
#import <unordered_map>
#define RCT_IS_TURBO_MODULE_CLASS(klass) \
((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)]))
#define RCT_IS_TURBO_MODULE_INSTANCE(module) RCT_IS_TURBO_MODULE_CLASS([(module) class])
typedef int MethodCallId;
/**
* This interface exists to allow the application to collect performance
* metrics of the TurboModule system. By implementing each function, you can
* hook into various stages of TurboModule creation and method dispatch (both async and sync).
*
* Note:
* - TurboModule async method invocations can interleave, so methodCallId should be used as a unique id for a method
* call.
*/
@protocol RCTTurboModulePerformanceLogger
// Create TurboModule JS Object
- (void)createTurboModuleStart:(const char *)moduleName;
- (void)createTurboModuleEnd:(const char *)moduleName;
- (void)createTurboModuleCacheHit:(const char *)moduleName;
- (void)getCppTurboModuleFromTMMDelegateStart:(const char *)moduleName;
- (void)getCppTurboModuleFromTMMDelegateEnd:(const char *)moduleName;
- (void)getTurboModuleFromRCTTurboModuleStart:(const char *)moduleName;
- (void)getTurboModuleFromRCTTurboModuleEnd:(const char *)moduleName;
- (void)getTurboModuleFromRCTCxxModuleStart:(const char *)moduleName;
- (void)getTurboModuleFromRCTCxxModuleEnd:(const char *)moduleName;
- (void)getTurboModuleFromTMMDelegateStart:(const char *)moduleName;
- (void)getTurboModuleFromTMMDelegateEnd:(const char *)moduleName;
// Create RCTTurboModule object
- (void)createRCTTurboModuleStart:(const char *)moduleName;
- (void)createRCTTurboModuleEnd:(const char *)moduleName;
- (void)createRCTTurboModuleCacheHit:(const char *)moduleName;
- (void)getRCTTurboModuleClassStart:(const char *)moduleName;
- (void)getRCTTurboModuleClassEnd:(const char *)moduleName;
- (void)getRCTTurboModuleInstanceStart:(const char *)moduleName;
- (void)getRCTTurboModuleInstanceEnd:(const char *)moduleName;
- (void)setupRCTTurboModuleDispatch:(const char *)moduleName;
- (void)setupRCTTurboModuleStart:(const char *)moduleName;
- (void)setupRCTTurboModuleEnd:(const char *)moduleName;
- (void)attachRCTBridgeToRCTTurboModuleStart:(const char *)moduleName;
- (void)attachRCTBridgeToRCTTurboModuleEnd:(const char *)moduleName;
- (void)attachMethodQueueToRCTTurboModuleStart:(const char *)moduleName;
- (void)attachMethodQueueToRCTTurboModuleEnd:(const char *)moduleName;
- (void)registerRCTTurboModuleForFrameUpdatesStart:(const char *)moduleName;
- (void)registerRCTTurboModuleForFrameUpdatesEnd:(const char *)moduleName;
- (void)dispatchDidInitializeModuleNotificationForRCTTurboModuleStart:(const char *)moduleName;
- (void)dispatchDidInitializeModuleNotificationForRCTTurboModuleEnd:(const char *)moduleName;
// Sync method invocation
- (void)syncMethodCallStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncMethodCallEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncMethodCallArgumentConversionStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncMethodCallArgumentConversionEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncRCTTurboModuleMethodCallStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncRCTTurboModuleMethodCallEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncMethodCallReturnConversionStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)syncMethodCallReturnConversionEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
// Async method invocation
- (void)asyncMethodCallStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncMethodCallEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncMethodCallArgumentConversionStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncMethodCallArgumentConversionEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncRCTTurboModuleMethodCallDispatch:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncRCTTurboModuleMethodCallStart:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
- (void)asyncRCTTurboModuleMethodCallEnd:(const char *)moduleName
methodName:(const char *)methodName
methodCallId:(MethodCallId)methodCallId;
@end
namespace facebook {
namespace react {
class Instance;
/**
* ObjC++ specific TurboModule base class.
*/
class JSI_EXPORT ObjCTurboModule : public TurboModule {
public:
ObjCTurboModule(
const std::string &name,
id<RCTTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker,
id<RCTTurboModulePerformanceLogger> perfLogger);
jsi::Value invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
SEL selector,
const jsi::Value *args,
size_t count);
id<RCTTurboModule> instance_;
std::shared_ptr<CallInvoker> nativeInvoker_;
protected:
void setMethodArgConversionSelector(NSString *methodName, int argIndex, NSString *fnName);
private:
/**
* TODO(ramanpreet):
* Investigate an optimization that'll let us get rid of this NSMutableDictionary.
*/
NSMutableDictionary<NSString *, NSMutableArray *> *methodArgConversionSelectors_;
NSDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames_;
NSString *getArgumentTypeName(NSString *methodName, int argIndex);
id<RCTTurboModulePerformanceLogger> performanceLogger_;
/**
* Required for performance logging async method invocations.
* This field is static because two nth async method calls from different
* TurboModules can interleave, and should therefore be treated as two distinct calls.
*/
static MethodCallId methodCallId_;
static MethodCallId getNewMethodCallId();
NSInvocation *getMethodInvocation(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation,
MethodCallId methodCallId);
jsi::Value performMethodInvocation(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation,
MethodCallId methodCallId);
BOOL hasMethodArgConversionSelector(NSString *methodName, int argIndex);
SEL getMethodArgConversionSelector(NSString *methodName, int argIndex);
using PromiseInvocationBlock = void (^)(RCTPromiseResolveBlock resolveWrapper, RCTPromiseRejectBlock rejectWrapper);
jsi::Value
createPromise(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> jsInvoker, PromiseInvocationBlock invoke);
};
} // namespace react
} // namespace facebook
@protocol RCTTurboModule <NSObject>
@optional
/**
* Used by TurboModules to get access to other TurboModules.
*
* Usage:
* Place `@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate`
* in the @implementation section of your TurboModule.
*/
@property (nonatomic, weak) id<RCTTurboModuleLookupDelegate> turboModuleLookupDelegate;
@optional
// This should be required, after migration is done.
- (std::shared_ptr<facebook::react::TurboModule>)
getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger;
@end
/**
* These methods are all implemented by RCTCxxBridge, which subclasses RCTBridge. Hence, they must only be used in
* contexts where the concrete class of an RCTBridge instance is RCTCxxBridge. This happens, for example, when
* [RCTCxxBridgeDelegate jsExecutorFactoryForBridge:(RCTBridge *)] is invoked by RCTCxxBridge.
*
* TODO: Consolidate this extension with the one in RCTSurfacePresenter.
*/
@interface RCTBridge (RCTTurboModule)
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
- (std::shared_ptr<facebook::react::CallInvoker>)decorateNativeCallInvoker:
(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker;
@end

View File

@ -0,0 +1,706 @@
/*
* 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/message.h>
#import <objc/runtime.h>
#import <sstream>
#import <vector>
#import <React/RCTBridgeModule.h>
#import <React/RCTConvert.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <React/RCTModuleMethod.h>
#import <React/RCTUtils.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/LongLivedObject.h>
#import <ReactCommon/TurboModule.h>
#import <ReactCommon/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::CallInvoker> 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::CallInvoker> jsInvoker)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
[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::CallInvoker> 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);
if (v) {
result[k] = v;
}
}
return [result copy];
}
static RCTResponseSenderBlock convertJSIFunctionToCallback(
jsi::Runtime &runtime,
const jsi::Function &value,
std::shared_ptr<react::CallInvoker> jsInvoker);
static id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
std::shared_ptr<react::CallInvoker> 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::CallInvoker> jsInvoker)
{
auto weakWrapper = react::CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker);
BOOL __block wrapperWasCalled = NO;
return ^(NSArray *responses) {
if (wrapperWasCalled) {
throw std::runtime_error("callback arg cannot be called more than once");
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> args = convertNSArrayToStdVector(strongWrapper2->runtime(), responses);
strongWrapper2->callback().call(strongWrapper2->runtime(), (const jsi::Value *)args.data(), args.size());
strongWrapper2->destroy();
});
wrapperWasCalled = YES;
};
}
namespace facebook {
namespace react {
jsi::Value ObjCTurboModule::createPromise(
jsi::Runtime &runtime,
std::shared_ptr<react::CallInvoker> 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 must pass constructor function two args. Passed " + std::to_string(count) + " args.");
}
if (!invokeCopy) {
return jsi::Value::undefined();
}
auto weakResolveWrapper =
react::CallbackWrapper::createWeak(args[0].getObject(rt).getFunction(rt), rt, jsInvoker);
auto weakRejectWrapper =
react::CallbackWrapper::createWeak(args[1].getObject(rt).getFunction(rt), rt, jsInvoker);
__block BOOL resolveWasCalled = NO;
__block BOOL rejectWasCalled = NO;
RCTPromiseResolveBlock resolveBlock = ^(id result) {
if (rejectWasCalled) {
throw std::runtime_error("Tried to resolve a promise after it's already been rejected.");
}
if (resolveWasCalled) {
throw std::runtime_error("Tried to resolve a promise more than once.");
}
auto strongResolveWrapper = weakResolveWrapper.lock();
auto strongRejectWrapper = weakRejectWrapper.lock();
if (!strongResolveWrapper || !strongRejectWrapper) {
return;
}
strongResolveWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, result]() {
auto strongResolveWrapper2 = weakResolveWrapper.lock();
auto strongRejectWrapper2 = weakRejectWrapper.lock();
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
return;
}
jsi::Runtime &rt = strongResolveWrapper2->runtime();
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
strongResolveWrapper2->callback().call(rt, arg);
strongResolveWrapper2->destroy();
strongRejectWrapper2->destroy();
});
resolveWasCalled = YES;
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
if (resolveWasCalled) {
throw std::runtime_error("Tried to reject a promise after it's already been resolved.");
}
if (rejectWasCalled) {
throw std::runtime_error("Tried to reject a promise more than once.");
}
auto strongResolveWrapper = weakResolveWrapper.lock();
auto strongRejectWrapper = weakRejectWrapper.lock();
if (!strongResolveWrapper || !strongRejectWrapper) {
return;
}
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
strongRejectWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, jsError]() {
auto strongResolveWrapper2 = weakResolveWrapper.lock();
auto strongRejectWrapper2 = weakRejectWrapper.lock();
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
return;
}
jsi::Runtime &rt = strongRejectWrapper2->runtime();
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
strongRejectWrapper2->callback().call(rt, arg);
strongResolveWrapper2->destroy();
strongRejectWrapper2->destroy();
});
rejectWasCalled = YES;
};
invokeCopy(resolveBlock, rejectBlock);
return jsi::Value::undefined();
});
return Promise.callAsConstructor(runtime, fn);
}
/**
* 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.
*/
jsi::Value ObjCTurboModule::performMethodInvocation(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation,
MethodCallId methodCallId)
{
__block id result;
jsi::Runtime *rt = &runtime;
__weak id<RCTTurboModule> weakModule = instance_;
id<RCTTurboModulePerformanceLogger> performanceLogger = performanceLogger_;
const char *moduleName = name_.c_str();
const bool isSync = returnType != VoidKind && returnType != PromiseKind;
void (^block)() = ^{
if (!weakModule) {
return;
}
id<RCTTurboModule> strongModule = weakModule;
if (isSync) {
[performanceLogger syncRCTTurboModuleMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId];
} else {
[performanceLogger asyncRCTTurboModuleMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId];
}
[inv invokeWithTarget:strongModule];
[retainedObjectsForInvocation removeAllObjects];
if (returnType == VoidKind) {
[performanceLogger asyncRCTTurboModuleMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId];
return;
}
void *rawResult;
[inv getReturnValue:&rawResult];
result = (__bridge id)rawResult;
[performanceLogger syncRCTTurboModuleMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId];
};
if (returnType == VoidKind) {
nativeInvoker_->invokeAsync([block]() -> void { block(); });
} else {
nativeInvoker_->invokeSync([block]() -> void { block(); });
}
// VoidKind can't be null
// PromiseKind, and FunctionKind must throw errors always
if (returnType != VoidKind && returnType != PromiseKind && returnType != FunctionKind &&
(result == (id)kCFNull || result == nil)) {
return jsi::Value::null();
}
jsi::Value returnValue = jsi::Value::undefined();
[performanceLogger_ syncMethodCallReturnConversionStart:moduleName methodName:methodName methodCallId:methodCallId];
// TODO: Re-use value conversion logic from existing impl, if possible.
switch (returnType) {
case VoidKind: {
break;
}
case BooleanKind: {
returnValue = convertNSNumberToJSIBoolean(*rt, (NSNumber *)result);
break;
}
case NumberKind: {
returnValue = convertNSNumberToJSINumber(*rt, (NSNumber *)result);
break;
}
case StringKind: {
returnValue = convertNSStringToJSIString(*rt, (NSString *)result);
break;
}
case ObjectKind: {
returnValue = convertNSDictionaryToJSIObject(*rt, (NSDictionary *)result);
break;
}
case ArrayKind: {
returnValue = convertNSArrayToJSIArray(*rt, (NSArray *)result);
break;
}
case FunctionKind:
throw std::runtime_error("convertInvocationResultToJSIValue: FunctionKind is not supported yet.");
case PromiseKind:
throw std::runtime_error("convertInvocationResultToJSIValue: PromiseKind wasn't handled properly.");
}
[performanceLogger_ syncMethodCallReturnConversionEnd:moduleName methodName:methodName methodCallId:methodCallId];
return returnValue;
}
/**
* Given a method name, and an argument index, return type of that argument.
* Prerequisite: You must wrap the method declaration inside some variant of the
* RCT_EXPORT_METHOD macro.
*
* This method returns nil if the method for which you're querying the argument type
* is not wrapped in an RCT_EXPORT_METHOD.
*
* Note: This is only being introduced for backward compatibility. It will be removed
* in the future.
*/
NSString *ObjCTurboModule::getArgumentTypeName(NSString *methodName, int argIndex)
{
if (!methodArgumentTypeNames_) {
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
unsigned int numberOfMethods;
Class cls = [instance_ class];
Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods);
if (methods) {
for (unsigned int i = 0; i < numberOfMethods; i++) {
SEL s = method_getName(methods[i]);
NSString *mName = NSStringFromSelector(s);
if (![mName hasPrefix:@"__rct_export__"]) {
continue;
}
// Message dispatch logic from old infra
RCTMethodInfo *(*getMethodInfo)(id, SEL) = (__typeof__(getMethodInfo))objc_msgSend;
RCTMethodInfo *methodInfo = getMethodInfo(cls, s);
NSArray<RCTMethodArgument *> *arguments;
NSString *otherMethodName = RCTParseMethodSignature(methodInfo->objcName, &arguments);
NSMutableArray *argumentTypes = [NSMutableArray arrayWithCapacity:[arguments count]];
for (int j = 0; j < [arguments count]; j += 1) {
[argumentTypes addObject:arguments[j].type];
}
NSString *normalizedOtherMethodName = [otherMethodName componentsSeparatedByString:@":"][0];
methodArgumentTypeNames[normalizedOtherMethodName] = argumentTypes;
}
free(methods);
}
methodArgumentTypeNames_ = methodArgumentTypeNames;
}
if (methodArgumentTypeNames_[methodName]) {
assert([methodArgumentTypeNames_[methodName] count] > argIndex);
return methodArgumentTypeNames_[methodName][argIndex];
}
return nil;
}
NSInvocation *ObjCTurboModule::getMethodInvocation(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation,
MethodCallId methodCallId)
{
const bool isSync = returnType != VoidKind && returnType != PromiseKind;
const char *moduleName = name_.c_str();
const id<RCTTurboModule> module = instance_;
if (isSync) {
[performanceLogger_ syncMethodCallArgumentConversionStart:moduleName
methodName:methodName
methodCallId:methodCallId];
} else {
[performanceLogger_ asyncMethodCallArgumentConversionStart:moduleName
methodName:methodName
methodCallId:methodCallId];
}
NSInvocation *inv =
[NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
[inv setSelector:selector];
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
for (size_t i = 0; i < count; i++) {
const jsi::Value *arg = &args[i];
const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2];
if (arg->isBool()) {
bool v = arg->getBool();
/**
* JS type checking ensures the Objective C argument here is either a BOOL or NSNumber*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithBool:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
continue;
}
if (arg->isNumber()) {
double v = arg->getNumber();
/**
* JS type checking ensures the Objective C argument here is either a double or NSNumber*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithDouble:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
continue;
}
/**
* Convert arg to ObjC objects.
*/
id objCArg = convertJSIValueToObjCObject(runtime, *arg, jsInvoker_);
if (objCArg) {
NSString *methodNameNSString = @(methodName);
/**
* Convert objects using RCTConvert.
*/
if (objCArgType == @encode(id)) {
NSString *argumentType = getArgumentTypeName(methodNameNSString, i);
if (argumentType != nil) {
NSString *rctConvertMethodName = [NSString stringWithFormat:@"%@:", argumentType];
SEL rctConvertSelector = NSSelectorFromString(rctConvertMethodName);
if ([RCTConvert respondsToSelector:rctConvertSelector]) {
// Message dispatch logic from old infra
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
id convertedObjCArg = convert([RCTConvert class], rctConvertSelector, objCArg);
[inv setArgument:(void *)&convertedObjCArg atIndex:i + 2];
if (convertedObjCArg) {
[retainedObjectsForInvocation addObject:convertedObjCArg];
}
continue;
}
}
}
/**
* Convert objects using RCTCxxConvert to structs.
*/
if ([objCArg isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameNSString, i)) {
SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameNSString, i);
// Message dispatch logic from old infra (link: https://git.io/fjf3U)
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, objCArg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:i + 2];
[retainedObjectsForInvocation addObject:box];
continue;
}
}
/**
* Insert converted args unmodified.
*/
[inv setArgument:(void *)&objCArg atIndex:i + 2];
if (objCArg) {
[retainedObjectsForInvocation addObject:objCArg];
}
}
if (isSync) {
[performanceLogger_ syncMethodCallArgumentConversionEnd:moduleName methodName:methodName methodCallId:methodCallId];
} else {
[performanceLogger_ asyncMethodCallArgumentConversionEnd:moduleName
methodName:methodName
methodCallId:methodCallId];
}
return inv;
}
ObjCTurboModule::ObjCTurboModule(
const std::string &name,
id<RCTTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker,
id<RCTTurboModulePerformanceLogger> perfLogger)
: TurboModule(name, jsInvoker), instance_(instance), nativeInvoker_(nativeInvoker), performanceLogger_(perfLogger)
{
}
MethodCallId ObjCTurboModule::methodCallId_{0};
MethodCallId ObjCTurboModule::getNewMethodCallId()
{
return methodCallId_++;
}
jsi::Value ObjCTurboModule::invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const std::string &methodNameStr,
SEL selector,
const jsi::Value *args,
size_t count)
{
MethodCallId methodCallId = getNewMethodCallId();
const bool isSync = returnType != VoidKind && returnType != PromiseKind;
const char *moduleName = name_.c_str();
const char *methodName = methodNameStr.c_str();
if (isSync) {
[performanceLogger_ syncMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId];
} else {
[performanceLogger_ asyncMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId];
}
NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2];
NSInvocation *inv = getMethodInvocation(
runtime, returnType, methodName, selector, args, count, retainedObjectsForInvocation, methodCallId);
jsi::Value returnValue = returnType == PromiseKind
? createPromise(
runtime,
jsInvoker_,
^(RCTPromiseResolveBlock resolveBlock, RCTPromiseRejectBlock rejectBlock) {
[inv setArgument:(void *)&resolveBlock atIndex:count + 2];
[inv setArgument:(void *)&rejectBlock atIndex:count + 3];
[retainedObjectsForInvocation addObject:resolveBlock];
[retainedObjectsForInvocation addObject:rejectBlock];
// The return type becomes void in the ObjC side.
performMethodInvocation(runtime, VoidKind, methodName, inv, retainedObjectsForInvocation, methodCallId);
})
: performMethodInvocation(runtime, returnType, methodName, inv, retainedObjectsForInvocation, methodCallId);
if (isSync) {
[performanceLogger_ syncMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId];
} else {
[performanceLogger_ asyncMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId];
}
return returnValue;
}
BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, int argIndex)
{
return methodArgConversionSelectors_ && methodArgConversionSelectors_[methodName] &&
![methodArgConversionSelectors_[methodName][argIndex] isEqual:[NSNull null]];
}
SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, int argIndex)
{
assert(hasMethodArgConversionSelector(methodName, argIndex));
return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue;
}
void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, int argIndex, NSString *fnName)
{
if (!methodArgConversionSelectors_) {
methodArgConversionSelectors_ = [NSMutableDictionary new];
}
if (!methodArgConversionSelectors_[methodName]) {
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:[NSNull null]];
}
}
SEL selector = NSSelectorFromString(fnName);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
methodArgConversionSelectors_[methodName][argIndex] = selectorValue;
}
} // namespace react
} // namespace facebook

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 "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::CallInvoker>)jsInvoker
nativeInvoker:
(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger;
@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::CallInvoker>)jsInvoker;
@end
@interface RCTTurboModuleManager : NSObject <RCTTurboModuleLookupDelegate>
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
performanceLogger:(id<RCTTurboModulePerformanceLogger>)performanceLogger;
- (void)installJSBindingWithRuntime:(facebook::jsi::Runtime *)runtime;
- (void)invalidate;
@end

View File

@ -0,0 +1,648 @@
/*
* 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 <atomic>
#import <cassert>
#import <mutex>
#import <objc/runtime.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxModule.h>
#import <React/RCTLog.h>
#import <React/RCTModuleData.h>
#import <React/RCTPerformanceLogger.h>
#import <React/RCTUtils.h>
#import <ReactCommon/TurboCxxModule.h>
#import <ReactCommon/TurboModuleBinding.h>
using namespace facebook;
/**
* A global variable whose address we use to associate method queues to id<RCTTurboModule> objects.
*/
static char kAssociatedMethodQueueKey;
namespace {
class MethodQueueNativeCallInvoker : public facebook::react::CallInvoker {
private:
dispatch_queue_t methodQueue_;
public:
MethodQueueNativeCallInvoker(dispatch_queue_t methodQueue) : methodQueue_(methodQueue) {}
void invokeAsync(std::function<void()> &&work) override
{
if (methodQueue_ == RCTJSThread) {
work();
return;
}
__block auto retainedWork = std::move(work);
dispatch_async(methodQueue_, ^{
retainedWork();
});
}
void invokeSync(std::function<void()> &&work) override
{
if (methodQueue_ == RCTJSThread) {
work();
return;
}
__block auto retainedWork = std::move(work);
dispatch_sync(methodQueue_, ^{
retainedWork();
});
}
};
}
// 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::CallInvoker> _jsInvoker;
id<RCTTurboModulePerformanceLogger> _performanceLogger;
__weak id<RCTTurboModuleManagerDelegate> _delegate;
__weak RCTBridge *_bridge;
/**
* TODO(T48018690):
* All modules are currently long-lived.
* We need to come up with a mechanism to allow modules to specify whether
* they want to be long-lived or short-lived.
*/
std::unordered_map<std::string, id<RCTTurboModule>> _rctTurboModuleCache;
std::unordered_map<std::string, std::shared_ptr<react::TurboModule>> _turboModuleCache;
/**
* _rctTurboModuleCache can be accessed by multiple threads at once via
* the provideRCTTurboModule method. This can lead to races. Therefore, we
* need to protect access to this unordered_map.
*
* Note:
* There's no need to protect access to _turboModuleCache because that cache
* is only accessed within provideTurboModule, which is only invoked by the
* JS thread.
*/
std::mutex _rctTurboModuleCacheLock;
std::atomic<bool> _invalidating;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return [self initWithBridge:bridge delegate:delegate jsInvoker:jsInvoker performanceLogger:nil];
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
performanceLogger:(id<RCTTurboModulePerformanceLogger>)performanceLogger
{
if (self = [super init]) {
_jsInvoker = jsInvoker;
_delegate = delegate;
_bridge = bridge;
_invalidating = false;
_performanceLogger = performanceLogger;
// Necessary to allow NativeModules to lookup TurboModules
[bridge setRCTTurboModuleLookupDelegate:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeWillInvalidateModules:)
name:RCTBridgeWillInvalidateModulesNotification
object:_bridge.parentBridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidInvalidateModules:)
name:RCTBridgeDidInvalidateModulesNotification
object:_bridge.parentBridge];
}
return self;
}
- (void)notifyAboutTurboModuleSetup:(const char *)name
{
NSString *moduleName = [[NSString alloc] initWithUTF8String:name];
if (moduleName) {
int64_t setupTime = [self->_bridge.performanceLogger durationForTag:RCTPLTurboModuleSetup];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidSetupModuleNotification
object:nil
userInfo:@{
RCTDidSetupModuleNotificationModuleNameKey : moduleName,
RCTDidSetupModuleNotificationSetupTimeKey : @(setupTime)
}];
}
}
/**
* Given a name for a TurboModule, return a C++ object which is the instance
* of that TurboModule C++ class. This class wraps the TurboModule's ObjC instance.
* If no TurboModule ObjC class exist with the provided name, abort program.
*
* Note: All TurboModule instances are cached, which means they're all long-lived
* (for now).
*/
- (std::shared_ptr<react::TurboModule>)provideTurboModule:(const char *)moduleName
{
auto turboModuleLookup = _turboModuleCache.find(moduleName);
if (turboModuleLookup != _turboModuleCache.end()) {
[_performanceLogger createTurboModuleCacheHit:moduleName];
return turboModuleLookup->second;
}
/**
* Step 1: Look for pure C++ modules.
* Pure C++ modules get priority.
*/
if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
[_performanceLogger getCppTurboModuleFromTMMDelegateStart:moduleName];
auto turboModule = [_delegate getTurboModule:moduleName jsInvoker:_jsInvoker];
[_performanceLogger getCppTurboModuleFromTMMDelegateEnd:moduleName];
if (turboModule != nullptr) {
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
}
/**
* Step 2: Look for platform-specific modules.
*/
[_performanceLogger createRCTTurboModuleStart:moduleName];
id<RCTTurboModule> module = [self provideRCTTurboModule:moduleName];
[_performanceLogger createRCTTurboModuleEnd:moduleName];
// If we request that a TurboModule be created, its respective ObjC class must exist
// If the class doesn't exist, then provideRCTTurboModule returns nil
if (!module) {
return nullptr;
}
Class moduleClass = [module class];
dispatch_queue_t methodQueue = (dispatch_queue_t)objc_getAssociatedObject(module, &kAssociatedMethodQueueKey);
/**
* Step 2c: Create and native CallInvoker from the TurboModule's method queue.
*/
std::shared_ptr<facebook::react::CallInvoker> nativeInvoker =
std::make_shared<MethodQueueNativeCallInvoker>(methodQueue);
/**
* Have RCTCxxBridge decorate native CallInvoker, so that it's aware of TurboModule async method calls.
* This helps the bridge fire onBatchComplete as readily as it should.
*/
if ([_bridge respondsToSelector:@selector(decorateNativeCallInvoker:)]) {
nativeInvoker = [_bridge decorateNativeCallInvoker:nativeInvoker];
}
// If RCTTurboModule supports creating its own C++ TurboModule object,
// allow it to do so.
if ([module respondsToSelector:@selector(getTurboModuleWithJsInvoker:nativeInvoker:perfLogger:)]) {
[_performanceLogger getTurboModuleFromRCTTurboModuleStart:moduleName];
auto turboModule = [module getTurboModuleWithJsInvoker:_jsInvoker
nativeInvoker:nativeInvoker
perfLogger:_performanceLogger];
[_performanceLogger getTurboModuleFromRCTTurboModuleEnd:moduleName];
assert(turboModule != nullptr);
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
/**
* Step 2d: If the moduleClass is a legacy CxxModule, return a TurboCxxModule instance that
* wraps CxxModule.
*/
if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) {
// Use TurboCxxModule compat class to wrap the CxxModule instance.
// This is only for migration convenience, despite less performant.
[_performanceLogger getTurboModuleFromRCTCxxModuleStart:moduleName];
auto turboModule = std::make_shared<react::TurboCxxModule>([((RCTCxxModule *)module) createModule], _jsInvoker);
[_performanceLogger getTurboModuleFromRCTCxxModuleEnd:moduleName];
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
/**
* Step 2e: Return an exact sub-class of ObjC TurboModule
*/
[_performanceLogger getTurboModuleFromTMMDelegateStart:moduleName];
auto turboModule = [_delegate getTurboModule:moduleName
instance:module
jsInvoker:_jsInvoker
nativeInvoker:nativeInvoker
perfLogger:_performanceLogger];
[_performanceLogger getTurboModuleFromTMMDelegateEnd:moduleName];
if (turboModule != nullptr) {
_turboModuleCache.insert({moduleName, turboModule});
}
return turboModule;
}
/**
* Given a name for a TurboModule, return an ObjC object which is the instance
* of that TurboModule ObjC class. If no TurboModule exist with the provided name,
* return nil.
*
* Note: All TurboModule instances are cached, which means they're all long-lived
* (for now).
*/
- (id<RCTTurboModule>)provideRCTTurboModule:(const char *)moduleName
{
Class moduleClass;
id<RCTTurboModule> module = nil;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
auto rctTurboModuleCacheLookup = _rctTurboModuleCache.find(moduleName);
if (rctTurboModuleCacheLookup != _rctTurboModuleCache.end()) {
[_performanceLogger createRCTTurboModuleCacheHit:moduleName];
return rctTurboModuleCacheLookup->second;
}
if (_invalidating) {
// Don't allow creating new instances while invalidating.
return nil;
}
/**
* Step 2a: Resolve platform-specific class.
*/
[_performanceLogger getRCTTurboModuleClassStart:moduleName];
if ([_delegate respondsToSelector:@selector(getModuleClassFromName:)]) {
moduleClass = [_delegate getModuleClassFromName:moduleName];
}
if (!moduleClass) {
moduleClass = getFallbackClassFromName(moduleName);
}
[_performanceLogger getRCTTurboModuleClassEnd:moduleName];
if (![moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
return nil;
}
/**
* Step 2b: Ask hosting application/delegate to instantiate this class
*/
[_performanceLogger getRCTTurboModuleInstanceStart:moduleName];
if ([_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) {
module = [_delegate getModuleInstanceFromClass:moduleClass];
} else {
module = [moduleClass new];
}
[_performanceLogger getRCTTurboModuleInstanceEnd:moduleName];
if ([module respondsToSelector:@selector(setTurboModuleLookupDelegate:)]) {
[module setTurboModuleLookupDelegate:self];
}
_rctTurboModuleCache.insert({moduleName, module});
}
[self setUpRCTTurboModule:module moduleName:moduleName];
return module;
}
- (void)setUpRCTTurboModule:(id<RCTTurboModule>)module moduleName:(const char *)moduleName
{
__weak id<RCTBridgeModule> weakModule = (id<RCTBridgeModule>)module;
__weak RCTBridge *weakBridge = _bridge;
id<RCTTurboModulePerformanceLogger> performanceLogger = _performanceLogger;
auto setUpTurboModule = ^{
if (!weakModule) {
return;
}
[performanceLogger setupRCTTurboModuleStart:moduleName];
id<RCTBridgeModule> strongModule = weakModule;
RCTBridge *strongBridge = weakBridge;
/**
* 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 ([strongModule respondsToSelector:@selector(bridge)] && strongBridge) {
[performanceLogger attachRCTBridgeToRCTTurboModuleStart:moduleName];
/**
* 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)strongModule setValue:strongBridge 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([strongModule class]));
}
[performanceLogger attachRCTBridgeToRCTTurboModuleEnd:moduleName];
}
/**
* Some modules need their own queues, but don't provide any, so we need to create it for them.
* These modules typically have the following:
* `@synthesize methodQueue = _methodQueue`
*/
[performanceLogger attachMethodQueueToRCTTurboModuleStart:moduleName];
dispatch_queue_t methodQueue = nil;
BOOL moduleHasMethodQueueGetter = [strongModule respondsToSelector:@selector(methodQueue)];
if (moduleHasMethodQueueGetter) {
methodQueue = [strongModule methodQueue];
}
/**
* Note: RCTJSThread, which is a valid method queue, is defined as (id)kCFNull. It should rightfully not enter the
* following if condition's block.
*/
if (!methodQueue) {
NSString *methodQueueName = [NSString stringWithFormat:@"com.facebook.react.%sQueue", moduleName];
methodQueue = dispatch_queue_create(methodQueueName.UTF8String, DISPATCH_QUEUE_SERIAL);
if (moduleHasMethodQueueGetter) {
/**
* If the module has a method queue getter, two cases are possible:
* - We @synthesized the method queue. In this case, the getter will initially return nil.
* - We had a custom methodQueue function on the NativeModule. If we got this far, then that getter returned
* nil.
*
* Therefore, we do a try/catch and use ObjC's KVC API and try to assign the method queue to the NativeModule.
* In case 1, we'll succeed. In case 2, an exception will be thrown, which we'll ignore.
*/
@try {
[(id)strongModule setValue:methodQueue forKey:@"methodQueue"];
} @catch (NSException *exception) {
RCTLogError(
@"%@ has no setter or ivar for its methodQueue, which is not "
"permitted. You must either @synthesize the bridge property, "
"or provide your own setter method.",
RCTBridgeModuleNameForClass([strongModule class]));
}
}
}
/**
* Attach method queue to id<RCTTurboModule> object.
* This is necessary because the id<RCTTurboModule> object can be eagerly created/initialized before the method
* queue is required. The method queue is required for an id<RCTTurboModule> for JS -> Native calls. So, we need it
* before we create the id<RCTTurboModule>'s TurboModule jsi::HostObject in provideTurboModule:.
*/
objc_setAssociatedObject(strongModule, &kAssociatedMethodQueueKey, methodQueue, OBJC_ASSOCIATION_RETAIN);
[performanceLogger attachMethodQueueToRCTTurboModuleEnd:moduleName];
/**
* NativeModules that implement the RCTFrameUpdateObserver protocol
* require registration with RCTDisplayLink.
*
* TODO(T55504345): Investigate whether we can improve this after TM
* rollout.
*/
if (strongBridge) {
[performanceLogger registerRCTTurboModuleForFrameUpdatesStart:moduleName];
RCTModuleData *data = [[RCTModuleData alloc] initWithModuleInstance:strongModule bridge:strongBridge];
[strongBridge registerModuleForFrameUpdates:strongModule withModuleData:data];
[performanceLogger registerRCTTurboModuleForFrameUpdatesEnd:moduleName];
}
/**
* Broadcast that this TurboModule was created.
*
* TODO(T41180176): Investigate whether we can delete this after TM
* rollout.
*/
[performanceLogger dispatchDidInitializeModuleNotificationForRCTTurboModuleStart:moduleName];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTDidInitializeModuleNotification
object:strongBridge
userInfo:@{@"module" : module, @"bridge" : RCTNullIfNil([strongBridge parentBridge])}];
[performanceLogger dispatchDidInitializeModuleNotificationForRCTTurboModuleEnd:moduleName];
[performanceLogger setupRCTTurboModuleEnd:moduleName];
};
/**
* TODO(T64991809): Fix TurboModule race:
* - When NativeModules that don't require main queue setup are required from different threads, they'll
* concurrently run setUpRCTTurboModule:
*/
if ([[module class] respondsToSelector:@selector(requiresMainQueueSetup)] &&
[[module class] requiresMainQueueSetup]) {
/**
* If the main thread synchronously calls into JS that creates a TurboModule,
* we could deadlock. This behaviour is migrated over from the legacy NativeModule
* system.
*
* TODO(T63807674): Investigate the right migration plan off of this
*/
[_performanceLogger setupRCTTurboModuleDispatch:moduleName];
RCTUnsafeExecuteOnMainQueueSync(setUpTurboModule);
} else {
setUpTurboModule();
}
}
- (void)installJSBindingWithRuntime:(jsi::Runtime *)runtime
{
_runtime = runtime;
if (!_runtime) {
// jsi::Runtime doesn't exist when attached to Chrome debugger.
return;
}
__weak __typeof(self) weakSelf = self;
react::TurboModuleBinding::install(
*_runtime,
[weakSelf,
performanceLogger = _performanceLogger](const std::string &name) -> std::shared_ptr<react::TurboModule> {
if (!weakSelf) {
return nullptr;
}
__strong __typeof(self) strongSelf = weakSelf;
auto moduleName = name.c_str();
auto moduleWasNotInitialized = ![strongSelf moduleIsInitialized:moduleName];
if (moduleWasNotInitialized) {
[strongSelf->_bridge.performanceLogger markStartForTag:RCTPLTurboModuleSetup];
}
[performanceLogger createTurboModuleStart:moduleName];
/**
* By default, all TurboModules are long-lived.
* Additionally, if a TurboModule with the name `name` isn't found, then we
* trigger an assertion failure.
*/
auto turboModule = [strongSelf provideTurboModule:moduleName];
[performanceLogger createTurboModuleEnd:moduleName];
if (moduleWasNotInitialized && [strongSelf moduleIsInitialized:moduleName]) {
[strongSelf->_bridge.performanceLogger markStopForTag:RCTPLTurboModuleSetup];
[strongSelf notifyAboutTurboModuleSetup:moduleName];
}
return turboModule;
});
}
#pragma mark RCTTurboModuleLookupDelegate
- (id)moduleForName:(const char *)moduleName
{
return [self moduleForName:moduleName warnOnLookupFailure:YES];
}
- (id)moduleForName:(const char *)moduleName warnOnLookupFailure:(BOOL)warnOnLookupFailure
{
id<RCTTurboModule> module = [self provideRCTTurboModule:moduleName];
if (warnOnLookupFailure && !module) {
RCTLogError(@"Unable to find module for %@", [NSString stringWithUTF8String:moduleName]);
}
return module;
}
- (BOOL)moduleIsInitialized:(const char *)moduleName
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
return _rctTurboModuleCache.find(std::string(moduleName)) != _rctTurboModuleCache.end();
}
#pragma mark Invalidation logic
- (void)bridgeWillInvalidateModules:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _bridge) {
return;
}
_invalidating = true;
}
- (void)bridgeDidInvalidateModules:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _bridge) {
return;
}
std::unordered_map<std::string, id<RCTTurboModule>> rctCacheCopy;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
rctCacheCopy.insert(_rctTurboModuleCache.begin(), _rctTurboModuleCache.end());
}
// Backward-compatibility: RCTInvalidating handling.
dispatch_group_t moduleInvalidationGroup = dispatch_group_create();
for (const auto &p : rctCacheCopy) {
id<RCTTurboModule> module = p.second;
if ([module respondsToSelector:@selector(invalidate)]) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t methodQueue = [module performSelector:@selector(methodQueue)];
if (methodQueue) {
dispatch_group_enter(moduleInvalidationGroup);
[bridge
dispatchBlock:^{
[((id<RCTInvalidating>)module) invalidate];
dispatch_group_leave(moduleInvalidationGroup);
}
queue:methodQueue];
continue;
}
}
[((id<RCTInvalidating>)module) invalidate];
}
}
if (dispatch_group_wait(moduleInvalidationGroup, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) {
RCTLogError(@"TurboModuleManager: Timed out waiting for modules to be invalidated");
}
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
_rctTurboModuleCache.clear();
}
_turboModuleCache.clear();
}
- (void)invalidate
{
std::unordered_map<std::string, id<RCTTurboModule>> rctCacheCopy;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
rctCacheCopy.insert(_rctTurboModuleCache.begin(), _rctTurboModuleCache.end());
}
// Backward-compatibility: RCTInvalidating handling, but not adhering to desired methodQueue.
for (const auto &p : rctCacheCopy) {
id<RCTTurboModule> module = p.second;
if ([module respondsToSelector:@selector(invalidate)]) {
[((id<RCTInvalidating>)module) invalidate];
}
}
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
_rctTurboModuleCache.clear();
}
_turboModuleCache.clear();
}
@end

View File

@ -0,0 +1,79 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "OBJC_ARC_PREPROCESSOR_FLAGS", "get_preprocessor_flags_for_build_mode", "get_static_library_ios_flags")
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "subdir_glob")
rn_xplat_cxx_library(
name = "samples",
srcs = glob(["*.cpp"]),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "ReactCommon",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbandroid_deps = [
react_native_target("jni/react/jni:jni"),
],
fbandroid_exported_headers = subdir_glob(
[
("platform/android", "*.h"),
],
prefix = "ReactCommon",
),
fbandroid_srcs = glob(
[
"platform/android/**/*.cpp",
],
),
fbobjc_compiler_flags = [
"-Wall",
"-fobjc-arc-exceptions",
],
fbobjc_inherited_buck_flags = get_static_library_ios_flags(),
fbobjc_preprocessor_flags = OBJC_ARC_PREPROCESSOR_FLAGS + get_preprocessor_flags_for_build_mode(),
force_static = True,
ios_deps = [
"//xplat/FBBaseLite:FBBaseLite",
"//xplat/js:RCTLinking",
"//xplat/js:RCTPushNotification",
"//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 = "ReactCommon",
),
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\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
react_native_xplat_target("cxxreact:module"),
],
exported_deps = [
"//xplat/jsi:jsi",
react_native_xplat_target("turbomodule/core:core"),
],
)

View File

@ -0,0 +1,141 @@
/*
* 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.
*/
#include "NativeSampleTurboCxxModuleSpecJSI.h"
// NOTE: This entire file should be codegen'ed.
namespace facebook {
namespace react {
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)->voidFunc(rt);
return jsi::Value::undefined();
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getBool(rt, args[0].getBool()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getNumber(rt, args[0].getNumber()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getString(rt, args[0].getString(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getArray(rt, args[0].getObject(rt).getArray(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getObject(rt, args[0].getObject(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getValue(
rt,
args[0].getNumber(),
args[1].getString(rt),
args[2].getObject(rt));
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getValueWithCallback(
rt, std::move(args[0].getObject(rt).getFunction(rt)));
return jsi::Value::undefined();
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getValueWithPromise(rt, args[0].getBool());
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI *>(&turboModule)
->getConstants(rt);
}
NativeSampleTurboCxxModuleSpecJSI::NativeSampleTurboCxxModuleSpecJSI(
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("SampleTurboCxxModule", jsInvoker) {
methodMap_["voidFunc"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool};
methodMap_["getNumber"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject};
methodMap_["getValue"] = MethodMetadata{
3, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise};
methodMap_["getConstants"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants};
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <string>
#include <ReactCommon/TurboModule.h>
namespace facebook {
namespace react {
// TODO: This definition should be codegen'ed for type-safety purpose.
class JSI_EXPORT NativeSampleTurboCxxModuleSpecJSI : public TurboModule {
protected:
NativeSampleTurboCxxModuleSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
public:
virtual void voidFunc(jsi::Runtime &rt) = 0;
virtual bool getBool(jsi::Runtime &rt, bool arg) = 0;
virtual double getNumber(jsi::Runtime &rt, double arg) = 0;
virtual jsi::String getString(jsi::Runtime &rt, const jsi::String &arg) = 0;
virtual jsi::Array getArray(jsi::Runtime &rt, const jsi::Array &arg) = 0;
virtual jsi::Object getObject(jsi::Runtime &rt, const jsi::Object &arg) = 0;
virtual jsi::Object getValue(
jsi::Runtime &rt,
double x,
const jsi::String &y,
const jsi::Object &z) = 0;
virtual void getValueWithCallback(
jsi::Runtime &rt,
const jsi::Function &callback) = 0;
virtual jsi::Value getValueWithPromise(jsi::Runtime &rt, bool error) = 0;
virtual jsi::Object getConstants(jsi::Runtime &rt) = 0;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,94 @@
/*
* 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 "SampleTurboCxxModule.h"
#import <ReactCommon/TurboModuleUtils.h>
using namespace facebook;
namespace facebook {
namespace react {
SampleTurboCxxModule::SampleTurboCxxModule(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleTurboCxxModuleSpecJSI(jsInvoker) {}
void SampleTurboCxxModule::voidFunc(jsi::Runtime &rt) {
// Nothing to do
}
bool SampleTurboCxxModule::getBool(jsi::Runtime &rt, bool arg) {
return arg;
}
double SampleTurboCxxModule::getNumber(jsi::Runtime &rt, double arg) {
return arg;
}
jsi::String SampleTurboCxxModule::getString(
jsi::Runtime &rt,
const jsi::String &arg) {
return jsi::String::createFromUtf8(rt, arg.utf8(rt));
}
jsi::Array SampleTurboCxxModule::getArray(
jsi::Runtime &rt,
const jsi::Array &arg) {
return deepCopyJSIArray(rt, arg);
}
jsi::Object SampleTurboCxxModule::getObject(
jsi::Runtime &rt,
const jsi::Object &arg) {
return deepCopyJSIObject(rt, arg);
}
jsi::Object SampleTurboCxxModule::getValue(
jsi::Runtime &rt,
double x,
const jsi::String &y,
const jsi::Object &z) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "x", jsi::Value(x));
result.setProperty(rt, "y", jsi::String::createFromUtf8(rt, y.utf8(rt)));
result.setProperty(rt, "z", deepCopyJSIObject(rt, z));
return result;
}
void SampleTurboCxxModule::getValueWithCallback(
jsi::Runtime &rt,
const jsi::Function &callback) {
callback.call(rt, jsi::String::createFromUtf8(rt, "value from callback!"));
}
jsi::Value SampleTurboCxxModule::getValueWithPromise(
jsi::Runtime &rt,
bool error) {
return createPromiseAsJSIValue(
rt, [error](jsi::Runtime &rt2, std::shared_ptr<Promise> promise) {
if (error) {
promise->reject("intentional promise rejection");
} else {
promise->resolve(jsi::String::createFromUtf8(rt2, "result!"));
}
});
}
jsi::Object SampleTurboCxxModule::getConstants(jsi::Runtime &rt) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "const1", jsi::Value(true));
result.setProperty(rt, "const2", jsi::Value(375));
result.setProperty(
rt, "const3", jsi::String::createFromUtf8(rt, "something"));
return result;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,44 @@
/*
* 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.
*/
#pragma once
#import <memory>
#import "NativeSampleTurboCxxModuleSpecJSI.h"
namespace facebook {
namespace react {
/**
* A sample implementation of the C++ spec. In practice, this class can just
* extend jsi::HostObject directly, but using the spec provides build-time
* type-safety.
*/
class SampleTurboCxxModule : public NativeSampleTurboCxxModuleSpecJSI {
public:
SampleTurboCxxModule(std::shared_ptr<CallInvoker> jsInvoker);
void voidFunc(jsi::Runtime &rt) override;
bool getBool(jsi::Runtime &rt, bool arg) override;
double getNumber(jsi::Runtime &rt, double arg) override;
jsi::String getString(jsi::Runtime &rt, const jsi::String &arg) override;
jsi::Array getArray(jsi::Runtime &rt, const jsi::Array &arg) override;
jsi::Object getObject(jsi::Runtime &rt, const jsi::Object &arg) override;
jsi::Object getValue(
jsi::Runtime &rt,
double x,
const jsi::String &y,
const jsi::Object &z) override;
void getValueWithCallback(jsi::Runtime &rt, const jsi::Function &callback)
override;
jsi::Value getValueWithPromise(jsi::Runtime &rt, bool error) override;
jsi::Object getConstants(jsi::Runtime &rt) override;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,53 @@
/*
* 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.
*/
// NOTE: This entire file should be codegen'ed.
#import <vector>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <ReactCommon/RCTTurboModule.h>
/**
* The ObjC protocol based on the JS Flow type for SampleTurboModule.
*/
@protocol NativeSampleTurboModuleSpec <RCTBridgeModule, RCTTurboModule>
- (void)voidFunc;
- (NSNumber *)getBool:(BOOL)arg;
- (NSNumber *)getNumber:(double)arg;
- (NSString *)getString:(NSString *)arg;
- (NSArray<id<NSObject>> *)getArray:(NSArray *)arg;
- (NSDictionary *)getObject:(NSDictionary *)arg;
- (NSDictionary *)getValue:(double)x y:(NSString *)y z:(NSDictionary *)z;
- (void)getValueWithCallback:(RCTResponseSenderBlock)callback;
- (void)getValueWithPromise:(BOOL)error resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (NSDictionary *)constantsToExport;
- (NSDictionary *)getConstants;
@end
namespace facebook {
namespace react {
/**
* The iOS TurboModule impl specific to SampleTurboModule.
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule {
public:
NativeSampleTurboModuleSpecJSI(
id<RCTTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker,
id<RCTTurboModulePerformanceLogger> perfLogger);
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,136 @@
/*
* 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 "RCTNativeSampleTurboModuleSpec.h"
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFunc", @selector(voidFunc), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, BooleanKind, "getBool", @selector(getBool:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getNumber", @selector(getNumber:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, StringKind, "getString", @selector(getString:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ArrayKind, "getArray", @selector(getArray:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObject", @selector(getObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getValue", @selector(getValue:y:z:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "getValueWithCallback", @selector(getValueWithCallback:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(
rt, PromiseKind, "getValueWithPromise", @selector(getValueWithPromise:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
id<RCTTurboModule> instance,
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker,
id<RCTTurboModulePerformanceLogger> perfLogger)
: ObjCTurboModule("SampleTurboModule", instance, jsInvoker, nativeInvoker, perfLogger)
{
methodMap_["voidFunc"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getNumber"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getValue"] = MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#import <React/RCTCxxModule.h>
#import <ReactCommon/RCTTurboModule.h>
/**
* Sample backward-compatible RCTCxxModule-based module.
* With jsi::HostObject, this class is no longer necessary, but the system supports it for
* backward compatibility.
*/
@interface RCTSampleTurboCxxModule_v1 : RCTCxxModule <RCTTurboModule>
@end
/**
* Second variant of a sample backward-compatible RCTCxxModule-based module.
*/
@interface RCTSampleTurboCxxModule_v2 : RCTCxxModule <RCTTurboModule>
@end

View File

@ -0,0 +1,42 @@
/*
* 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 "RCTSampleTurboCxxModule.h"
#import <ReactCommon/SampleTurboCxxModule.h>
#import <cxxreact/CxxModule.h>
#import "SampleTurboCxxModuleLegacyImpl.h"
using namespace facebook;
// ObjC++ wrapper.
@implementation RCTSampleTurboCxxModule_v1
RCT_EXPORT_MODULE();
- (std::shared_ptr<react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<react::CallInvoker>)jsInvoker
{
return std::make_shared<react::SampleTurboCxxModule>(jsInvoker);
}
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return nullptr;
}
@end
@implementation RCTSampleTurboCxxModule_v2
RCT_EXPORT_MODULE();
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return std::make_unique<react::SampleTurboCxxModuleLegacyImpl>();
}
@end

View File

@ -0,0 +1,18 @@
/*
* 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 <Foundation/Foundation.h>
#import "RCTNativeSampleTurboModuleSpec.h"
/**
* Sample iOS-specific impl of a TurboModule, conforming to the spec protocol.
* This class is also 100% compatible with the NativeModule system.
*/
@interface RCTSampleTurboModule : NSObject <NativeSampleTurboModuleSpec>
@end

View File

@ -0,0 +1,132 @@
/*
* 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 "RCTSampleTurboModule.h"
#import <UIKit/UIKit.h>
using namespace facebook::react;
@implementation RCTSampleTurboModule
// Backward-compatible export
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate;
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
- (std::shared_ptr<facebook::react::TurboModule>)
getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
{
return std::make_shared<NativeSampleTurboModuleSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
return @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : y ?: [NSNull null],
@"z" : z ?: [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (!callback) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(getValueWithPromise
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
if (!resolve || !reject) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
@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.
*/
#include "SampleTurboCxxModuleLegacyImpl.h"
#include <cxxreact/JsArgumentHelpers.h>
using namespace facebook::xplat::module;
namespace facebook {
namespace react {
SampleTurboCxxModuleLegacyImpl::SampleTurboCxxModuleLegacyImpl() {}
std::string SampleTurboCxxModuleLegacyImpl::getName() {
return "SampleTurboCxxModule_v2";
}
std::map<std::string, folly::dynamic>
SampleTurboCxxModuleLegacyImpl::getConstants() {
return {
{"const1", true},
{"const2", 375},
{"const3", "something"},
};
};
std::vector<CxxModule::Method> SampleTurboCxxModuleLegacyImpl::getMethods() {
return {
CxxModule::Method(
"voidFunc", [this](folly::dynamic args) { voidFunc(); }),
CxxModule::Method(
"getBool",
[this](folly::dynamic args) {
return getBool(xplat::jsArgAsBool(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getNumber",
[this](folly::dynamic args) {
return getNumber(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getArray",
[this](folly::dynamic args) {
return getArray(xplat::jsArgAsArray(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getObject",
[this](folly::dynamic args) {
return getObject(xplat::jsArgAsObject(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValue",
[this](folly::dynamic args) {
return getValue(
xplat::jsArgAsDouble(args, 0),
xplat::jsArgAsString(args, 1),
xplat::jsArgAsObject(args, 2));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValueWithCallback",
[this](folly::dynamic args, CxxModule::Callback callback) {
getValueWithCallback(callback);
}),
CxxModule::Method(
"getValueWithPromise",
[this](
folly::dynamic args,
CxxModule::Callback resolve,
CxxModule::Callback reject) {
getValueWithPromise(xplat::jsArgAsBool(args, 0), resolve, reject);
}),
};
};
void SampleTurboCxxModuleLegacyImpl::voidFunc() {
// Do nothing.
}
bool SampleTurboCxxModuleLegacyImpl::getBool(bool arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getNumber(double arg) {
return arg;
}
std::string SampleTurboCxxModuleLegacyImpl::getString(const std::string &arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getArray(
const folly::dynamic &arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getObject(
const folly::dynamic &arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getValue(
double x,
const std::string &y,
const folly::dynamic &z) {
return folly::dynamic::object("x", x)("y", y)("z", z);
}
void SampleTurboCxxModuleLegacyImpl::getValueWithCallback(
const CxxModule::Callback &callback) {
callback({"value from callback!"});
}
void SampleTurboCxxModuleLegacyImpl::getValueWithPromise(
bool error,
const CxxModule::Callback &resolve,
const CxxModule::Callback &reject) {
if (!error) {
resolve({"result!"});
} else {
reject(
{folly::dynamic::object("message", "intentional promise rejection")});
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,45 @@
/*
* 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.
*/
#pragma once
#import <cxxreact/CxxModule.h>
namespace facebook {
namespace react {
/**
* A sample CxxModule (legacy system) implementation.
*/
class SampleTurboCxxModuleLegacyImpl
: public facebook::xplat::module::CxxModule {
public:
SampleTurboCxxModuleLegacyImpl();
std::string getName() override;
std::map<std::string, folly::dynamic> getConstants() override;
std::vector<facebook::xplat::module::CxxModule::Method> getMethods() override;
// API
void voidFunc();
bool getBool(bool arg);
double getNumber(double arg);
std::string getString(const std::string &arg);
folly::dynamic getArray(const folly::dynamic &arg);
folly::dynamic getObject(const folly::dynamic &arg);
folly::dynamic
getValue(double x, const std::string &y, const folly::dynamic &z);
void getValueWithCallback(
const facebook::xplat::module::CxxModule::Callback &callback);
void getValueWithPromise(
bool error,
const facebook::xplat::module::CxxModule::Callback &resolve,
const facebook::xplat::module::CxxModule::Callback &reject);
};
} // namespace react
} // namespace facebook