Delete jsi::Functions before jsi::Runtime gets deleted
Summary: ## The Problem 1. `CatalystInstanceImpl` indirectly holds on to the `jsi::Runtime`. When you destroy `CatalystInstanceImpl`, you destroy the `jsi::Runtime`. As a part of reloading React Native, we destroy and re-create `CatalystInstanceImpl`, which destroys and re-creates the `jsi::Runtime`. 2. When JS passes in a callback to a TurboModule method, we take that callback (a `jsi::Function`) and wrap it in a Java `Callback` (implemented by `JCxxCallbackImpl`). This Java `Callback`, when executed, schedules the `jsi::Function` to be invoked on a Java thread at a later point in time. **Note:** The Java NativeModule can hold on to the Java `Callback` (and, by transitivity, the `jsi::Function`) for potentially forever. 3. It is a requirement of `jsi::Runtime` that all objects associated with the Runtime (ex: `jsi::Function`) must be destroyed before the Runtime itself is destroyed. See: https://fburl.com/m3mqk6wt ### jsi.h ``` /// .................................................... In addition, to /// make shutdown safe, destruction of objects associated with the Runtime /// must be destroyed before the Runtime is destroyed, or from the /// destructor of a managed HostObject or HostFunction. Informally, this /// means that the main source of unsafe behavior is to hold a jsi object /// in a non-Runtime-managed object, and not clean it up before the Runtime /// is shut down. If your lifecycle is such that avoiding this is hard, /// you will probably need to do use your own locks. class Runtime { public: virtual ~Runtime(); ``` Therefore, when you delete `CatalystInstanceImpl`, you could end up with a situation where the `jsi::Runtime` is destroyed before all `jsi::Function`s are destroyed. In dev, this leads the program to crash when you reload the app after having used a TurboModule method that uses callbacks. ## The Solution If the only reference to a `HostObject` or a `HostFunction` is in the JS Heap, then the `HostObject` and `HostFunction` destructors can destroy JSI objects. The TurboModule cache is the only thing, aside from the JS Heap, that holds a reference to all C++ TurboModules. But that cache (and the entire native side of `TurboModuleManager`) is destroyed when we call `mHybridData.resetNative()` in `TurboModuleManager.onCatalystInstanceDestroy()` in D16552730. (I verified this by commenting out `mHybridData.resetNative()` and placing a breakpoint in the destructor of `JavaTurboModule`). So, when we're cleaning up `TurboModuleManager`, the only reference to a Java TurboModule is the JS Heap. Therefore, it's safe and correct for us to destroy all `jsi::Function`s created by the Java TurboModule in `~JavaTurboModule`. So, in this diff, I keep a set of all `CallbackWrappers`, and explicitly call `destroy()` on them in the `JavaTurboModule` destructor. Note that since `~JavaTurboModule` accesses `callbackWrappers_`, it must be executed on the JS Thread, since `createJavaCallbackFromJSIFunction` also accesses `callbackWrappers_` on the JS Thread. For additional safety, I also eagerly destroyed the `jsi::Function` after it's been invoked once. I'm not yet sure if we only want JS callbacks to only ever be invoked once. So, I've created a Task to document this work: T48128233. Reviewed By: mdvacca Differential Revision: D16623340 fbshipit-source-id: 3a4c3efc70b9b3c8d329f19fdf4b4423c489695b
This commit is contained in:
Родитель
7c0b73501e
Коммит
bc7c85f153
|
@ -23,6 +23,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
|
||||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||||
import com.facebook.react.common.ReactConstants;
|
import com.facebook.react.common.ReactConstants;
|
||||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||||
|
import com.facebook.react.config.ReactFeatureFlags;
|
||||||
import com.facebook.react.module.annotations.ReactModule;
|
import com.facebook.react.module.annotations.ReactModule;
|
||||||
import com.facebook.react.turbomodule.core.JSCallInvokerHolderImpl;
|
import com.facebook.react.turbomodule.core.JSCallInvokerHolderImpl;
|
||||||
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
||||||
|
@ -359,6 +360,30 @@ public class CatalystInstanceImpl implements CatalystInstance {
|
||||||
listener.onBridgeDestroyed();
|
listener.onBridgeDestroyed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final JSIModule turboModuleManager =
|
||||||
|
ReactFeatureFlags.useTurboModules
|
||||||
|
? mJSIModuleRegistry.getModule(JSIModuleType.TurboModuleManager)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
getReactQueueConfiguration()
|
||||||
|
.getJSQueueThread()
|
||||||
|
.runOnQueue(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// We need to destroy the TurboModuleManager on the JS Thread
|
||||||
|
if (turboModuleManager != null) {
|
||||||
|
turboModuleManager.onCatalystInstanceDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
getReactQueueConfiguration()
|
||||||
|
.getUIQueueThread()
|
||||||
|
.runOnQueue(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// AsyncTask.execute must be executed from the UI Thread
|
||||||
AsyncTask.execute(
|
AsyncTask.execute(
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -366,15 +391,23 @@ public class CatalystInstanceImpl implements CatalystInstance {
|
||||||
// Kill non-UI threads from neutral third party
|
// Kill non-UI threads from neutral third party
|
||||||
// potentially expensive, so don't run on UI thread
|
// potentially expensive, so don't run on UI thread
|
||||||
|
|
||||||
// contextHolder is used as a lock to guard against other users of the JS VM
|
// contextHolder is used as a lock to guard against
|
||||||
// having
|
// other users of the JS VM having the VM destroyed
|
||||||
// the VM destroyed underneath them, so notify them before we resetNative
|
// underneath them, so notify them before we reset
|
||||||
|
// Native
|
||||||
mJavaScriptContextHolder.clear();
|
mJavaScriptContextHolder.clear();
|
||||||
|
|
||||||
mHybridData.resetNative();
|
mHybridData.resetNative();
|
||||||
getReactQueueConfiguration().destroy();
|
getReactQueueConfiguration().destroy();
|
||||||
Log.d(ReactConstants.TAG, "CatalystInstanceImpl.destroy() end");
|
Log.d(
|
||||||
ReactMarker.logMarker(ReactMarkerConstants.DESTROY_CATALYST_INSTANCE_END);
|
ReactConstants.TAG,
|
||||||
|
"CatalystInstanceImpl.destroy() end");
|
||||||
|
ReactMarker.logMarker(
|
||||||
|
ReactMarkerConstants.DESTROY_CATALYST_INSTANCE_END);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,15 @@ public class JSIModuleRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyJSInstanceDestroy() {
|
public void notifyJSInstanceDestroy() {
|
||||||
for (JSIModuleHolder moduleHolder : mModules.values()) {
|
for (Map.Entry<JSIModuleType, JSIModuleHolder> entry : mModules.entrySet()) {
|
||||||
|
JSIModuleType moduleType = entry.getKey();
|
||||||
|
|
||||||
|
// Don't call TurboModuleManager.onCatalystInstanceDestroy
|
||||||
|
if (moduleType == JSIModuleType.TurboModuleManager) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSIModuleHolder moduleHolder = entry.getValue();
|
||||||
moduleHolder.notifyJSInstanceDestroy();
|
moduleHolder.notifyJSInstanceDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,35 +22,49 @@ static CxxModule::Callback makeTurboCxxModuleCallback(
|
||||||
jsi::Runtime &runtime,
|
jsi::Runtime &runtime,
|
||||||
std::shared_ptr<CallbackWrapper> callbackWrapper) {
|
std::shared_ptr<CallbackWrapper> callbackWrapper) {
|
||||||
return [callbackWrapper](std::vector<folly::dynamic> args) {
|
return [callbackWrapper](std::vector<folly::dynamic> args) {
|
||||||
callbackWrapper->jsInvoker->invokeAsync([callbackWrapper, args]() {
|
callbackWrapper->jsInvoker().invokeAsync([callbackWrapper, args]() {
|
||||||
std::vector<jsi::Value> innerArgs;
|
std::vector<jsi::Value> innerArgs;
|
||||||
for (auto &a : args) {
|
for (auto &a : args) {
|
||||||
innerArgs.push_back(jsi::valueFromDynamic(callbackWrapper->runtime, a));
|
innerArgs.push_back(
|
||||||
|
jsi::valueFromDynamic(callbackWrapper->runtime(), a));
|
||||||
}
|
}
|
||||||
callbackWrapper->callback.call(callbackWrapper->runtime, (const jsi::Value *)innerArgs.data(), innerArgs.size());
|
callbackWrapper->callback().call(
|
||||||
|
callbackWrapper->runtime(),
|
||||||
|
(const jsi::Value *)innerArgs.data(),
|
||||||
|
innerArgs.size());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TurboCxxModule::TurboCxxModule(std::unique_ptr<CxxModule> cxxModule, std::shared_ptr<JSCallInvoker> jsInvoker)
|
TurboCxxModule::TurboCxxModule(
|
||||||
|
std::unique_ptr<CxxModule> cxxModule,
|
||||||
|
std::shared_ptr<JSCallInvoker> jsInvoker)
|
||||||
: TurboModule(cxxModule->getName(), jsInvoker),
|
: TurboModule(cxxModule->getName(), jsInvoker),
|
||||||
cxxMethods_(cxxModule->getMethods()),
|
cxxMethods_(cxxModule->getMethods()),
|
||||||
cxxModule_(std::move(cxxModule)) {}
|
cxxModule_(std::move(cxxModule)) {}
|
||||||
|
|
||||||
jsi::Value TurboCxxModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
|
jsi::Value TurboCxxModule::get(
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::PropNameID &propName) {
|
||||||
std::string propNameUtf8 = propName.utf8(runtime);
|
std::string propNameUtf8 = propName.utf8(runtime);
|
||||||
|
|
||||||
if (propNameUtf8 == "getConstants") {
|
if (propNameUtf8 == "getConstants") {
|
||||||
// This is special cased because `getConstants()` is already a part of CxxModule.
|
// This is special cased because `getConstants()` is already a part of
|
||||||
|
// CxxModule.
|
||||||
return jsi::Function::createFromHostFunction(
|
return jsi::Function::createFromHostFunction(
|
||||||
runtime,
|
runtime,
|
||||||
propName,
|
propName,
|
||||||
0,
|
0,
|
||||||
[this](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
|
[this](
|
||||||
|
jsi::Runtime &rt,
|
||||||
|
const jsi::Value &thisVal,
|
||||||
|
const jsi::Value *args,
|
||||||
|
size_t count) {
|
||||||
jsi::Object result(rt);
|
jsi::Object result(rt);
|
||||||
auto constants = cxxModule_->getConstants();
|
auto constants = cxxModule_->getConstants();
|
||||||
for (auto &pair : constants) {
|
for (auto &pair : constants) {
|
||||||
result.setProperty(rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second));
|
result.setProperty(
|
||||||
|
rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
@ -62,7 +76,11 @@ jsi::Value TurboCxxModule::get(jsi::Runtime& runtime, const jsi::PropNameID& pro
|
||||||
runtime,
|
runtime,
|
||||||
propName,
|
propName,
|
||||||
0,
|
0,
|
||||||
[this, propNameUtf8](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
|
[this, propNameUtf8](
|
||||||
|
jsi::Runtime &rt,
|
||||||
|
const jsi::Value &thisVal,
|
||||||
|
const jsi::Value *args,
|
||||||
|
size_t count) {
|
||||||
return invokeMethod(rt, VoidKind, propNameUtf8, args, count);
|
return invokeMethod(rt, VoidKind, propNameUtf8, args, count);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,7 +95,6 @@ jsi::Value TurboCxxModule::invokeMethod(
|
||||||
const std::string &methodName,
|
const std::string &methodName,
|
||||||
const jsi::Value *args,
|
const jsi::Value *args,
|
||||||
size_t count) {
|
size_t count) {
|
||||||
|
|
||||||
auto it = cxxMethods_.begin();
|
auto it = cxxMethods_.begin();
|
||||||
for (; it != cxxMethods_.end(); it++) {
|
for (; it != cxxMethods_.end(); it++) {
|
||||||
auto method = *it;
|
auto method = *it;
|
||||||
|
@ -87,7 +104,8 @@ jsi::Value TurboCxxModule::invokeMethod(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == cxxMethods_.end()) {
|
if (it == cxxMethods_.end()) {
|
||||||
throw std::runtime_error("Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
|
throw std::runtime_error(
|
||||||
|
"Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto method = *it;
|
auto method = *it;
|
||||||
|
@ -97,23 +115,37 @@ jsi::Value TurboCxxModule::invokeMethod(
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
|
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
|
||||||
}
|
}
|
||||||
return jsi::valueFromDynamic(runtime, method.syncFunc(std::move(innerArgs)));
|
return jsi::valueFromDynamic(
|
||||||
|
runtime, method.syncFunc(std::move(innerArgs)));
|
||||||
} else if (method.func && !method.isPromise) {
|
} else if (method.func && !method.isPromise) {
|
||||||
// Async method.
|
// Async method.
|
||||||
CxxModule::Callback first;
|
CxxModule::Callback first;
|
||||||
CxxModule::Callback second;
|
CxxModule::Callback second;
|
||||||
|
|
||||||
if (count < method.callbacks) {
|
if (count < method.callbacks) {
|
||||||
throw std::invalid_argument(folly::to<std::string>("Expected ", method.callbacks,
|
throw std::invalid_argument(folly::to<std::string>(
|
||||||
" callbacks, but only ", count, " parameters provided"));
|
"Expected ",
|
||||||
|
method.callbacks,
|
||||||
|
" callbacks, but only ",
|
||||||
|
count,
|
||||||
|
" parameters provided"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method.callbacks == 1) {
|
if (method.callbacks == 1) {
|
||||||
auto wrapper = std::make_shared<CallbackWrapper>(args[count - 1].getObject(runtime).getFunction(runtime), runtime, jsInvoker_);
|
auto wrapper = std::make_shared<CallbackWrapper>(
|
||||||
|
args[count - 1].getObject(runtime).getFunction(runtime),
|
||||||
|
runtime,
|
||||||
|
jsInvoker_);
|
||||||
first = makeTurboCxxModuleCallback(runtime, wrapper);
|
first = makeTurboCxxModuleCallback(runtime, wrapper);
|
||||||
} else if (method.callbacks == 2) {
|
} else if (method.callbacks == 2) {
|
||||||
auto wrapper1 = std::make_shared<CallbackWrapper>(args[count - 2].getObject(runtime).getFunction(runtime), runtime, jsInvoker_);
|
auto wrapper1 = std::make_shared<CallbackWrapper>(
|
||||||
auto wrapper2 = std::make_shared<CallbackWrapper>(args[count - 1].getObject(runtime).getFunction(runtime), runtime, jsInvoker_);
|
args[count - 2].getObject(runtime).getFunction(runtime),
|
||||||
|
runtime,
|
||||||
|
jsInvoker_);
|
||||||
|
auto wrapper2 = std::make_shared<CallbackWrapper>(
|
||||||
|
args[count - 1].getObject(runtime).getFunction(runtime),
|
||||||
|
runtime,
|
||||||
|
jsInvoker_);
|
||||||
first = makeTurboCxxModuleCallback(runtime, wrapper1);
|
first = makeTurboCxxModuleCallback(runtime, wrapper1);
|
||||||
second = makeTurboCxxModuleCallback(runtime, wrapper2);
|
second = makeTurboCxxModuleCallback(runtime, wrapper2);
|
||||||
}
|
}
|
||||||
|
@ -125,11 +157,18 @@ jsi::Value TurboCxxModule::invokeMethod(
|
||||||
|
|
||||||
method.func(std::move(innerArgs), first, second);
|
method.func(std::move(innerArgs), first, second);
|
||||||
} else if (method.isPromise) {
|
} else if (method.isPromise) {
|
||||||
return createPromiseAsJSIValue(runtime, [method, args, count, this](jsi::Runtime &rt, std::shared_ptr<Promise> promise) {
|
return createPromiseAsJSIValue(
|
||||||
auto resolveWrapper = std::make_shared<CallbackWrapper>(promise->resolve_.getFunction(rt), rt, jsInvoker_);
|
runtime,
|
||||||
auto rejectWrapper = std::make_shared<CallbackWrapper>(promise->reject_.getFunction(rt), rt, jsInvoker_);
|
[method, args, count, this](
|
||||||
CxxModule::Callback resolve = makeTurboCxxModuleCallback(rt, resolveWrapper);
|
jsi::Runtime &rt, std::shared_ptr<Promise> promise) {
|
||||||
CxxModule::Callback reject = makeTurboCxxModuleCallback(rt, rejectWrapper);
|
auto resolveWrapper = std::make_shared<CallbackWrapper>(
|
||||||
|
promise->resolve_.getFunction(rt), rt, jsInvoker_);
|
||||||
|
auto rejectWrapper = std::make_shared<CallbackWrapper>(
|
||||||
|
promise->reject_.getFunction(rt), rt, jsInvoker_);
|
||||||
|
CxxModule::Callback resolve =
|
||||||
|
makeTurboCxxModuleCallback(rt, resolveWrapper);
|
||||||
|
CxxModule::Callback reject =
|
||||||
|
makeTurboCxxModuleCallback(rt, rejectWrapper);
|
||||||
|
|
||||||
auto innerArgs = folly::dynamic::array();
|
auto innerArgs = folly::dynamic::array();
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <folly/Optional.h>
|
||||||
#include <jsi/jsi.h>
|
#include <jsi/jsi.h>
|
||||||
|
|
||||||
#include <ReactCommon/JSCallInvoker.h>
|
#include <ReactCommon/JSCallInvoker.h>
|
||||||
|
@ -32,19 +34,62 @@ struct Promise {
|
||||||
jsi::Function reject_;
|
jsi::Function reject_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PromiseSetupFunctionType = std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
|
using PromiseSetupFunctionType =
|
||||||
jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, const PromiseSetupFunctionType func);
|
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.
|
// Helper for passing jsi::Function arg to other methods.
|
||||||
struct CallbackWrapper {
|
class CallbackWrapper {
|
||||||
CallbackWrapper(jsi::Function callback, jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
private:
|
||||||
|
struct Data {
|
||||||
|
Data(
|
||||||
|
jsi::Function callback,
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
: callback(std::move(callback)),
|
: callback(std::move(callback)),
|
||||||
runtime(runtime),
|
runtime(runtime),
|
||||||
jsInvoker(jsInvoker) {}
|
jsInvoker(std::move(jsInvoker)) {}
|
||||||
|
|
||||||
jsi::Function callback;
|
jsi::Function callback;
|
||||||
jsi::Runtime &runtime;
|
jsi::Runtime &runtime;
|
||||||
std::shared_ptr<react::JSCallInvoker> jsInvoker;
|
std::shared_ptr<react::JSCallInvoker> jsInvoker;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
folly::Optional<Data> data_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CallbackWrapper(
|
||||||
|
jsi::Function callback,
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
: data_(Data{std::move(callback), runtime, jsInvoker}) {}
|
||||||
|
|
||||||
|
// Delete the enclosed jsi::Function
|
||||||
|
void destroy() {
|
||||||
|
data_ = folly::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDestroyed() {
|
||||||
|
return !data_.hasValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
jsi::Function &callback() {
|
||||||
|
assert(!isDestroyed());
|
||||||
|
return data_->callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsi::Runtime &runtime() {
|
||||||
|
assert(!isDestroyed());
|
||||||
|
return data_->runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
react::JSCallInvoker &jsInvoker() {
|
||||||
|
assert(!isDestroyed());
|
||||||
|
return *(data_->jsInvoker);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
} // namespace facebook
|
} // namespace facebook
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
#include <jsi/jsi.h>
|
#include <jsi/jsi.h>
|
||||||
|
|
||||||
#include <ReactCommon/TurboModule.h>
|
#include <ReactCommon/TurboModule.h>
|
||||||
#include <ReactCommon/TurboModuleUtils.h>
|
|
||||||
#include <jsi/JSIDynamic.h>
|
#include <jsi/JSIDynamic.h>
|
||||||
#include <react/jni/JCallback.h>
|
|
||||||
#include <react/jni/NativeMap.h>
|
#include <react/jni/NativeMap.h>
|
||||||
#include <react/jni/ReadableNativeMap.h>
|
#include <react/jni/ReadableNativeMap.h>
|
||||||
#include <react/jni/WritableNativeMap.h>
|
#include <react/jni/WritableNativeMap.h>
|
||||||
|
@ -31,35 +29,69 @@ JavaTurboModule::JavaTurboModule(
|
||||||
std::shared_ptr<JSCallInvoker> jsInvoker)
|
std::shared_ptr<JSCallInvoker> jsInvoker)
|
||||||
: TurboModule(name, jsInvoker), instance_(jni::make_global(instance)) {}
|
: TurboModule(name, jsInvoker), instance_(jni::make_global(instance)) {}
|
||||||
|
|
||||||
jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
|
jni::local_ref<JCxxCallbackImpl::JavaPart>
|
||||||
|
JavaTurboModule::createJavaCallbackFromJSIFunction(
|
||||||
jsi::Function &function,
|
jsi::Function &function,
|
||||||
jsi::Runtime &rt,
|
jsi::Runtime &rt,
|
||||||
std::shared_ptr<JSCallInvoker> jsInvoker) {
|
std::shared_ptr<JSCallInvoker> jsInvoker) {
|
||||||
auto wrapper = std::make_shared<react::CallbackWrapper>(
|
auto wrapper = std::make_shared<react::CallbackWrapper>(
|
||||||
std::move(function), rt, jsInvoker);
|
std::move(function), rt, jsInvoker);
|
||||||
std::function<void(folly::dynamic)> fn = [wrapper](folly::dynamic responses) {
|
callbackWrappers_.insert(wrapper);
|
||||||
if (wrapper == nullptr) {
|
|
||||||
|
std::function<void(folly::dynamic)> fn = [this,
|
||||||
|
wrapper](folly::dynamic responses) {
|
||||||
|
if (wrapper->isDestroyed()) {
|
||||||
throw std::runtime_error("callback arg cannot be called more than once");
|
throw std::runtime_error("callback arg cannot be called more than once");
|
||||||
}
|
}
|
||||||
std::shared_ptr<react::CallbackWrapper> rw = wrapper;
|
|
||||||
wrapper->jsInvoker->invokeAsync([rw, responses]() {
|
wrapper->jsInvoker().invokeAsync([this, wrapper, responses]() {
|
||||||
|
if (wrapper->isDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO (T43155926) valueFromDynamic already returns a Value array. Don't
|
// TODO (T43155926) valueFromDynamic already returns a Value array. Don't
|
||||||
// iterate again
|
// iterate again
|
||||||
jsi::Value args = jsi::valueFromDynamic(rw->runtime, responses);
|
jsi::Value args = jsi::valueFromDynamic(wrapper->runtime(), responses);
|
||||||
auto argsArray = args.getObject(rw->runtime).asArray(rw->runtime);
|
auto argsArray =
|
||||||
|
args.getObject(wrapper->runtime()).asArray(wrapper->runtime());
|
||||||
std::vector<jsi::Value> result;
|
std::vector<jsi::Value> result;
|
||||||
for (size_t i = 0; i < argsArray.size(rw->runtime); i++) {
|
for (size_t i = 0; i < argsArray.size(wrapper->runtime()); i++) {
|
||||||
result.emplace_back(
|
result.emplace_back(
|
||||||
rw->runtime, argsArray.getValueAtIndex(rw->runtime, i));
|
wrapper->runtime(),
|
||||||
|
argsArray.getValueAtIndex(wrapper->runtime(), i));
|
||||||
}
|
}
|
||||||
rw->callback.call(
|
wrapper->callback().call(
|
||||||
rw->runtime, (const jsi::Value *)result.data(), result.size());
|
wrapper->runtime(), (const jsi::Value *)result.data(), result.size());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eagerly destroy the jsi::Function since it's already been invoked.
|
||||||
|
* TODO(T48128233) Do we want callbacks to be invoked only once?
|
||||||
|
*
|
||||||
|
* NOTE: ~JavaTurboModule and this function run on the same thread.
|
||||||
|
* If you reach this point, you know that the destructor wasn't run
|
||||||
|
* because the current wrapper wasn't destroyed. Therefore, it's
|
||||||
|
* safe to access callbackWrappers_.
|
||||||
|
*/
|
||||||
|
wrapper->destroy();
|
||||||
|
callbackWrappers_.erase(wrapper);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
wrapper = nullptr;
|
|
||||||
return JCxxCallbackImpl::newObjectCxxArgs(fn);
|
return JCxxCallbackImpl::newObjectCxxArgs(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JavaTurboModule::~JavaTurboModule() {
|
||||||
|
/**
|
||||||
|
* Delete all jsi::Functions that haven't yet been invoked by Java.
|
||||||
|
* So long as nothing else aside from the JS heap is holding on to this
|
||||||
|
* JavaTurboModule, this destructor is guaranteed to execute before the
|
||||||
|
* jsi::Runtime is deleted.
|
||||||
|
*/
|
||||||
|
for (auto it = callbackWrappers_.begin(); it != callbackWrappers_.end();
|
||||||
|
it++) {
|
||||||
|
(*it)->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -184,7 +216,7 @@ std::vector<std::string> getMethodArgTypesFromSignature(
|
||||||
// needs to be done again
|
// needs to be done again
|
||||||
// TODO (axe) Reuse existing implementation as needed - the exist in
|
// TODO (axe) Reuse existing implementation as needed - the exist in
|
||||||
// MethodInvoker.cpp
|
// MethodInvoker.cpp
|
||||||
std::vector<jvalue> convertJSIArgsToJNIArgs(
|
std::vector<jvalue> JavaTurboModule::convertJSIArgsToJNIArgs(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jsi::Runtime &rt,
|
jsi::Runtime &rt,
|
||||||
std::string methodName,
|
std::string methodName,
|
||||||
|
|
|
@ -8,10 +8,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include <ReactCommon/TurboModule.h>
|
#include <ReactCommon/TurboModule.h>
|
||||||
|
#include <ReactCommon/TurboModuleUtils.h>
|
||||||
#include <fb/fbjni.h>
|
#include <fb/fbjni.h>
|
||||||
#include <jsi/jsi.h>
|
#include <jsi/jsi.h>
|
||||||
|
#include <react/jni/JCallback.h>
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
@ -35,9 +38,34 @@ class JSI_EXPORT JavaTurboModule : public TurboModule {
|
||||||
const jsi::Value *args,
|
const jsi::Value *args,
|
||||||
size_t count);
|
size_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This dtor must be called from the JS Thread, since it accesses
|
||||||
|
* callbackWrappers_, which createJavaCallbackFromJSIFunction also accesses
|
||||||
|
* from the JS Thread.
|
||||||
|
*/
|
||||||
|
virtual ~JavaTurboModule();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
jni::global_ref<JTurboModule> instance_;
|
jni::global_ref<JTurboModule> instance_;
|
||||||
jclass findClass(JNIEnv *env) const;
|
std::unordered_set<std::shared_ptr<CallbackWrapper>> callbackWrappers_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method must be called from the JS Thread, since it accesses
|
||||||
|
* callbackWrappers_.
|
||||||
|
*/
|
||||||
|
jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
|
||||||
|
jsi::Function &function,
|
||||||
|
jsi::Runtime &rt,
|
||||||
|
std::shared_ptr<JSCallInvoker> jsInvoker);
|
||||||
|
std::vector<jvalue> convertJSIArgsToJNIArgs(
|
||||||
|
JNIEnv *env,
|
||||||
|
jsi::Runtime &rt,
|
||||||
|
std::string methodName,
|
||||||
|
std::vector<std::string> methodArgTypes,
|
||||||
|
const jsi::Value *args,
|
||||||
|
size_t count,
|
||||||
|
std::shared_ptr<JSCallInvoker> jsInvoker,
|
||||||
|
TurboModuleMethodValueKind valueKind);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
|
|
|
@ -28,20 +28,24 @@ using namespace facebook;
|
||||||
/**
|
/**
|
||||||
* All static helper functions are ObjC++ specific.
|
* All static helper functions are ObjC++ specific.
|
||||||
*/
|
*/
|
||||||
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value) {
|
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
|
||||||
|
{
|
||||||
return jsi::Value((bool)[value boolValue]);
|
return jsi::Value((bool)[value boolValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value) {
|
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
|
||||||
|
{
|
||||||
return jsi::Value([value doubleValue]);
|
return jsi::Value([value doubleValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value) {
|
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
|
||||||
|
{
|
||||||
return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");
|
return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
|
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
|
||||||
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value) {
|
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
|
||||||
|
{
|
||||||
jsi::Object result = jsi::Object(runtime);
|
jsi::Object result = jsi::Object(runtime);
|
||||||
for (NSString *k in value) {
|
for (NSString *k in value) {
|
||||||
result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k]));
|
result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k]));
|
||||||
|
@ -49,7 +53,8 @@ static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDicti
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value) {
|
static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
|
||||||
|
{
|
||||||
jsi::Array result = jsi::Array(runtime, value.count);
|
jsi::Array result = jsi::Array(runtime, value.count);
|
||||||
for (size_t i = 0; i < value.count; i++) {
|
for (size_t i = 0; i < value.count; i++) {
|
||||||
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
|
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
|
||||||
|
@ -57,7 +62,8 @@ static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value) {
|
static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
|
||||||
|
{
|
||||||
std::vector<jsi::Value> result;
|
std::vector<jsi::Value> result;
|
||||||
for (size_t i = 0; i < value.count; i++) {
|
for (size_t i = 0; i < value.count; i++) {
|
||||||
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
|
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
|
||||||
|
@ -65,7 +71,8 @@ static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value) {
|
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
|
||||||
|
{
|
||||||
if ([value isKindOfClass:[NSString class]]) {
|
if ([value isKindOfClass:[NSString class]]) {
|
||||||
return convertNSStringToJSIString(runtime, (NSString *)value);
|
return convertNSStringToJSIString(runtime, (NSString *)value);
|
||||||
} else if ([value isKindOfClass:[NSNumber class]]) {
|
} else if ([value isKindOfClass:[NSNumber class]]) {
|
||||||
|
@ -83,22 +90,35 @@ static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value) {
|
||||||
return jsi::Value::undefined();
|
return jsi::Value::undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<react::JSCallInvoker> jsInvoker);
|
static id convertJSIValueToObjCObject(
|
||||||
static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value) {
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Value &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker);
|
||||||
|
static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
|
||||||
|
{
|
||||||
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
|
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<react::JSCallInvoker> jsInvoker) {
|
static NSArray *convertJSIArrayToNSArray(
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Array &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
{
|
||||||
size_t size = value.size(runtime);
|
size_t size = value.size(runtime);
|
||||||
NSMutableArray *result = [NSMutableArray new];
|
NSMutableArray *result = [NSMutableArray new];
|
||||||
for (size_t i = 0; i < size; i++) {
|
for (size_t i = 0; i < size; i++) {
|
||||||
// Insert kCFNull when it's `undefined` value to preserve the indices.
|
// Insert kCFNull when it's `undefined` value to preserve the indices.
|
||||||
[result addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
|
[result
|
||||||
|
addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
|
||||||
}
|
}
|
||||||
return [result copy];
|
return [result copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<react::JSCallInvoker> jsInvoker) {
|
static NSDictionary *convertJSIObjectToNSDictionary(
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Object &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
{
|
||||||
jsi::Array propertyNames = value.getPropertyNames(runtime);
|
jsi::Array propertyNames = value.getPropertyNames(runtime);
|
||||||
size_t size = propertyNames.size(runtime);
|
size_t size = propertyNames.size(runtime);
|
||||||
NSMutableDictionary *result = [NSMutableDictionary new];
|
NSMutableDictionary *result = [NSMutableDictionary new];
|
||||||
|
@ -113,8 +133,15 @@ static NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const
|
||||||
return [result copy];
|
return [result copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<react::JSCallInvoker> jsInvoker);
|
static RCTResponseSenderBlock convertJSIFunctionToCallback(
|
||||||
static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<react::JSCallInvoker> jsInvoker) {
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Function &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker);
|
||||||
|
static id convertJSIValueToObjCObject(
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Value &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
{
|
||||||
if (value.isUndefined() || value.isNull()) {
|
if (value.isUndefined() || value.isNull()) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +168,11 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v
|
||||||
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
|
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
|
||||||
}
|
}
|
||||||
|
|
||||||
static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<react::JSCallInvoker> jsInvoker) {
|
static RCTResponseSenderBlock convertJSIFunctionToCallback(
|
||||||
|
jsi::Runtime &runtime,
|
||||||
|
const jsi::Function &value,
|
||||||
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
{
|
||||||
__block auto wrapper = std::make_shared<react::CallbackWrapper>(value.getFunction(runtime), runtime, jsInvoker);
|
__block auto wrapper = std::make_shared<react::CallbackWrapper>(value.getFunction(runtime), runtime, jsInvoker);
|
||||||
return ^(NSArray *responses) {
|
return ^(NSArray *responses) {
|
||||||
if (wrapper == nullptr) {
|
if (wrapper == nullptr) {
|
||||||
|
@ -149,9 +180,9 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<react::CallbackWrapper> rw = wrapper;
|
std::shared_ptr<react::CallbackWrapper> rw = wrapper;
|
||||||
wrapper->jsInvoker->invokeAsync([rw, responses]() {
|
wrapper->jsInvoker().invokeAsync([rw, responses]() {
|
||||||
std::vector<jsi::Value> args = convertNSArrayToStdVector(rw->runtime, responses);
|
std::vector<jsi::Value> args = convertNSArrayToStdVector(rw->runtime(), responses);
|
||||||
rw->callback.call(rw->runtime, (const jsi::Value *)args.data(), args.size());
|
rw->callback().call(rw->runtime(), (const jsi::Value *)args.data(), args.size());
|
||||||
});
|
});
|
||||||
|
|
||||||
// The callback is single-use, so force release it here.
|
// The callback is single-use, so force release it here.
|
||||||
|
@ -167,7 +198,8 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
jsi::Function resolve,
|
jsi::Function resolve,
|
||||||
jsi::Function reject,
|
jsi::Function reject,
|
||||||
jsi::Runtime &runtime,
|
jsi::Runtime &runtime,
|
||||||
std::shared_ptr<react::JSCallInvoker> jsInvoker) {
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
||||||
|
{
|
||||||
auto instance = std::make_shared<PromiseWrapper>(std::move(resolve), std::move(reject), runtime, jsInvoker);
|
auto instance = std::make_shared<PromiseWrapper>(std::move(resolve), std::move(reject), runtime, jsInvoker);
|
||||||
// This instance needs to live longer than the caller's scope, since the resolve/reject functions may not
|
// This instance needs to live longer than the caller's scope, since the resolve/reject functions may not
|
||||||
// be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the
|
// be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the
|
||||||
|
@ -184,9 +216,12 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
: resolveWrapper(std::make_shared<react::CallbackWrapper>(std::move(resolve), runtime, jsInvoker)),
|
: resolveWrapper(std::make_shared<react::CallbackWrapper>(std::move(resolve), runtime, jsInvoker)),
|
||||||
rejectWrapper(std::make_shared<react::CallbackWrapper>(std::move(reject), runtime, jsInvoker)),
|
rejectWrapper(std::make_shared<react::CallbackWrapper>(std::move(reject), runtime, jsInvoker)),
|
||||||
runtime(runtime),
|
runtime(runtime),
|
||||||
jsInvoker(jsInvoker) {}
|
jsInvoker(jsInvoker)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
RCTPromiseResolveBlock resolveBlock() {
|
RCTPromiseResolveBlock resolveBlock()
|
||||||
|
{
|
||||||
return ^(id result) {
|
return ^(id result) {
|
||||||
if (resolveWrapper == nullptr) {
|
if (resolveWrapper == nullptr) {
|
||||||
throw std::runtime_error("Promise resolve arg cannot be called more than once");
|
throw std::runtime_error("Promise resolve arg cannot be called more than once");
|
||||||
|
@ -195,9 +230,9 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
// Retain the resolveWrapper so that it stays alive inside the lambda.
|
// Retain the resolveWrapper so that it stays alive inside the lambda.
|
||||||
std::shared_ptr<react::CallbackWrapper> retainedWrapper = resolveWrapper;
|
std::shared_ptr<react::CallbackWrapper> retainedWrapper = resolveWrapper;
|
||||||
jsInvoker->invokeAsync([retainedWrapper, result]() {
|
jsInvoker->invokeAsync([retainedWrapper, result]() {
|
||||||
jsi::Runtime &rt = retainedWrapper->runtime;
|
jsi::Runtime &rt = retainedWrapper->runtime();
|
||||||
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
|
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
|
||||||
retainedWrapper->callback.call(rt, arg);
|
retainedWrapper->callback().call(rt, arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent future invocation of the same resolve() function.
|
// Prevent future invocation of the same resolve() function.
|
||||||
|
@ -205,7 +240,8 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RCTPromiseRejectBlock rejectBlock() {
|
RCTPromiseRejectBlock rejectBlock()
|
||||||
|
{
|
||||||
return ^(NSString *code, NSString *message, NSError *error) {
|
return ^(NSString *code, NSString *message, NSError *error) {
|
||||||
// TODO: There is a chance `this` is no longer valid when this block executes.
|
// TODO: There is a chance `this` is no longer valid when this block executes.
|
||||||
if (rejectWrapper == nullptr) {
|
if (rejectWrapper == nullptr) {
|
||||||
|
@ -216,9 +252,9 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
std::shared_ptr<react::CallbackWrapper> retainedWrapper = rejectWrapper;
|
std::shared_ptr<react::CallbackWrapper> retainedWrapper = rejectWrapper;
|
||||||
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
|
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
|
||||||
jsInvoker->invokeAsync([retainedWrapper, jsError]() {
|
jsInvoker->invokeAsync([retainedWrapper, jsError]() {
|
||||||
jsi::Runtime &rt = retainedWrapper->runtime;
|
jsi::Runtime &rt = retainedWrapper->runtime();
|
||||||
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
|
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
|
||||||
retainedWrapper->callback.call(rt, arg);
|
retainedWrapper->callback().call(rt, arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent future invocation of the same resolve() function.
|
// Prevent future invocation of the same resolve() function.
|
||||||
|
@ -226,15 +262,16 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup() {
|
void cleanup()
|
||||||
|
{
|
||||||
resolveWrapper = nullptr;
|
resolveWrapper = nullptr;
|
||||||
rejectWrapper = nullptr;
|
rejectWrapper = nullptr;
|
||||||
allowRelease();
|
allowRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after either
|
// CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after
|
||||||
// the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need an extra
|
// either the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need
|
||||||
// mechanism to control that lifecycle.
|
// an extra mechanism to control that lifecycle.
|
||||||
std::shared_ptr<react::CallbackWrapper> resolveWrapper;
|
std::shared_ptr<react::CallbackWrapper> resolveWrapper;
|
||||||
std::shared_ptr<react::CallbackWrapper> rejectWrapper;
|
std::shared_ptr<react::CallbackWrapper> rejectWrapper;
|
||||||
jsi::Runtime &runtime;
|
jsi::Runtime &runtime;
|
||||||
|
@ -242,7 +279,9 @@ struct PromiseWrapper : public react::LongLivedObject {
|
||||||
};
|
};
|
||||||
|
|
||||||
using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper);
|
using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper);
|
||||||
static jsi::Value createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInvoker, PromiseInvocationBlock invoke) {
|
static jsi::Value
|
||||||
|
createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInvoker, PromiseInvocationBlock invoke)
|
||||||
|
{
|
||||||
if (!invoke) {
|
if (!invoke) {
|
||||||
return jsi::Value::undefined();
|
return jsi::Value::undefined();
|
||||||
}
|
}
|
||||||
|
@ -293,8 +332,8 @@ jsi::Value performMethodInvocation(
|
||||||
TurboModuleMethodValueKind valueKind,
|
TurboModuleMethodValueKind valueKind,
|
||||||
const id<RCTTurboModule> module,
|
const id<RCTTurboModule> module,
|
||||||
std::shared_ptr<JSCallInvoker> jsInvoker,
|
std::shared_ptr<JSCallInvoker> jsInvoker,
|
||||||
NSMutableArray *retainedObjectsForInvocation) {
|
NSMutableArray *retainedObjectsForInvocation)
|
||||||
|
{
|
||||||
__block id result;
|
__block id result;
|
||||||
jsi::Runtime *rt = &runtime;
|
jsi::Runtime *rt = &runtime;
|
||||||
void (^block)() = ^{
|
void (^block)() = ^{
|
||||||
|
@ -370,7 +409,8 @@ jsi::Value performMethodInvocation(
|
||||||
* Note: This is only being introduced for backward compatibility. It will be removed
|
* Note: This is only being introduced for backward compatibility. It will be removed
|
||||||
* in the future.
|
* in the future.
|
||||||
*/
|
*/
|
||||||
NSString* ObjCTurboModule::getArgumentTypeName(NSString* methodName, int argIndex) {
|
NSString *ObjCTurboModule::getArgumentTypeName(NSString *methodName, int argIndex)
|
||||||
|
{
|
||||||
if (!methodArgumentTypeNames_) {
|
if (!methodArgumentTypeNames_) {
|
||||||
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
|
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
|
||||||
|
|
||||||
|
@ -425,8 +465,10 @@ NSInvocation *ObjCTurboModule::getMethodInvocation(
|
||||||
SEL selector,
|
SEL selector,
|
||||||
const jsi::Value *args,
|
const jsi::Value *args,
|
||||||
size_t count,
|
size_t count,
|
||||||
NSMutableArray *retainedObjectsForInvocation) {
|
NSMutableArray *retainedObjectsForInvocation)
|
||||||
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
|
{
|
||||||
|
NSInvocation *inv =
|
||||||
|
[NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
|
||||||
[inv setSelector:selector];
|
[inv setSelector:selector];
|
||||||
|
|
||||||
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
|
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
|
||||||
|
@ -556,8 +598,9 @@ ObjCTurboModule::ObjCTurboModule(
|
||||||
const std::string &name,
|
const std::string &name,
|
||||||
id<RCTTurboModule> instance,
|
id<RCTTurboModule> instance,
|
||||||
std::shared_ptr<JSCallInvoker> jsInvoker)
|
std::shared_ptr<JSCallInvoker> jsInvoker)
|
||||||
: TurboModule(name, jsInvoker),
|
: TurboModule(name, jsInvoker), instance_(instance)
|
||||||
instance_(instance) {}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
jsi::Value ObjCTurboModule::invokeObjCMethod(
|
jsi::Value ObjCTurboModule::invokeObjCMethod(
|
||||||
jsi::Runtime &runtime,
|
jsi::Runtime &runtime,
|
||||||
|
@ -568,15 +611,13 @@ jsi::Value ObjCTurboModule::invokeObjCMethod(
|
||||||
size_t count)
|
size_t count)
|
||||||
{
|
{
|
||||||
NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2];
|
NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2];
|
||||||
NSInvocation *inv = getMethodInvocation(runtime, valueKind, instance_, jsInvoker_, methodName, selector, args, count, retainedObjectsForInvocation);
|
NSInvocation *inv = getMethodInvocation(
|
||||||
|
runtime, valueKind, instance_, jsInvoker_, methodName, selector, args, count, retainedObjectsForInvocation);
|
||||||
|
|
||||||
if (valueKind == PromiseKind) {
|
if (valueKind == PromiseKind) {
|
||||||
// Promise return type is special cased today, i.e. it needs extra 2 function args for resolve() and reject(), to
|
// Promise return type is special cased today, i.e. it needs extra 2 function args for resolve() and reject(), to
|
||||||
// be passed to the actual ObjC++ class method.
|
// be passed to the actual ObjC++ class method.
|
||||||
return createPromise(
|
return createPromise(runtime, jsInvoker_, ^(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper) {
|
||||||
runtime,
|
|
||||||
jsInvoker_,
|
|
||||||
^(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper) {
|
|
||||||
RCTPromiseResolveBlock resolveBlock = wrapper->resolveBlock();
|
RCTPromiseResolveBlock resolveBlock = wrapper->resolveBlock();
|
||||||
RCTPromiseRejectBlock rejectBlock = wrapper->rejectBlock();
|
RCTPromiseRejectBlock rejectBlock = wrapper->rejectBlock();
|
||||||
[inv setArgument:(void *)&resolveBlock atIndex:count + 2];
|
[inv setArgument:(void *)&resolveBlock atIndex:count + 2];
|
||||||
|
@ -591,16 +632,20 @@ jsi::Value ObjCTurboModule::invokeObjCMethod(
|
||||||
return performMethodInvocation(runtime, inv, valueKind, instance_, jsInvoker_, retainedObjectsForInvocation);
|
return performMethodInvocation(runtime, inv, valueKind, instance_, jsInvoker_, retainedObjectsForInvocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, int argIndex) {
|
BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, int argIndex)
|
||||||
return methodArgConversionSelectors_ && methodArgConversionSelectors_[methodName] && ![methodArgConversionSelectors_[methodName][argIndex] isEqual:[NSNull null]];
|
{
|
||||||
|
return methodArgConversionSelectors_ && methodArgConversionSelectors_[methodName] &&
|
||||||
|
![methodArgConversionSelectors_[methodName][argIndex] isEqual:[NSNull null]];
|
||||||
}
|
}
|
||||||
|
|
||||||
SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, int argIndex) {
|
SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, int argIndex)
|
||||||
|
{
|
||||||
assert(hasMethodArgConversionSelector(methodName, argIndex));
|
assert(hasMethodArgConversionSelector(methodName, argIndex));
|
||||||
return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue;
|
return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, int argIndex, NSString *fnName) {
|
void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, int argIndex, NSString *fnName)
|
||||||
|
{
|
||||||
if (!methodArgConversionSelectors_) {
|
if (!methodArgConversionSelectors_) {
|
||||||
methodArgConversionSelectors_ = [NSMutableDictionary new];
|
methodArgConversionSelectors_ = [NSMutableDictionary new];
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче