It was mentioned in the last article that after JSI, JS and Native hold a HostObject at the same time, so there are basic conditions for synchronous invocation between JS and Native.
JS calls Native synchronously
In fact, synchronous JS calls to Native code are implemented in RN (take version 0.59 as an example), and can be implemented in iOS via the RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD macro.
@implementation ConfigManager
RCT_EXPORT_MODULE();
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getApiUrl)
{
return API_URL;
}
@end
Copy the code
The call in JS is
import { NativeModules } from 'react-native';
const apiUrl = NativeModules.ConfigManager.getApiUrl();
Copy the code
Let’s take a look at how RN is implemented, first by looking at the macro definition and source code of the Native side, which can be traced back to
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime&,
const jsi::Value&,
const jsi::Value* args,
size_t count) { return nativeCallSyncHook(args, count); }));
Copy the code
Then look at the corresponding call in JS as
function genMethod(moduleID: number, methodID: number, type: MethodType) {
if (type= = ='promise') {... }else if (type= = ='sync') {
fn = function(... args: Array<any>) { ...returnglobal.nativeCallSyncHook(moduleID, methodID, args); }; }... }Copy the code
In fact, it is through JSI that nativeCallSyncHook, the HostObject, is created to realize the synchronous invocation of JS to Native.
Native calls JS synchronously
With JSI, we can complete the synchronous call from Native to JS. Now let’s try to implement the synchronization task of onScroll of ScrollView mentioned in the previous article.
Since synchronous JS calls to Native are implemented via nativeCallSyncHook, let’s implement a jsCallSyncHook that synchronously calls the runtime methods from Native threads (including the main thread).
Function code
What we want to achieve is to slide the ScrollView, pass its offset to the JS side for business logic processing, and then synchronously update the text of a Label on the current page. The code for updating Native page is:
int Test::runTest(Runtime& runtime, const Value& vl) {
// testA View is a UIView that contains UILabel and UIScrollView, Lb is the current UILabel lb. Text = [nsstrings stringWithUTF8String: vl. ToString (runtime). Utf8 (runtime). C_str ()]; [testView setNeedsLayout];
return 0;
}
Copy the code
Export HostObject to JS
Two methods need to be implemented. The first install() exports the global nativeTest property to the JS Runtime.
void TestBinding::install(Runtime &runtime, std::shared_ptr<TestBinding> testBinding) {
auto testModuleName = "nativeTest";
auto object = Object::createFromHostObject(runtime, testBinding);
runtime.global().setProperty(runtime, testModuleName,
std::move(object));
}
Copy the code
The second is the method runTest that rolls out global properties.
Value TestBinding::get(Runtime &runtime, const PropNameID &name) {
auto methodName = name.utf8(runtime);
if (methodName == "runTest") {
return Function::createFromHostFunction(runtime, name, 0, [&test](Runtime& runtime,
const Value &thisValue,
const Value *arguments,
size_t count) -> Value {
return test.runTest(runtime, *arguments);
});
}
return Value::undefined();
}
Copy the code
You then need to bindin the appropriate place (such as the view component init) by calling install().
auto test = std::make_unique<Test>();
std::shared_ptr<TestBinding> testBinding_ = std::make_shared<TestBinding>(std::move(test));
TestBinding::install(runtime, testBinding_);
Copy the code
We call the JS Runtime global jsCallSyncHook object while onScroll and pass in the offset value of ScrollView.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
Runtime* runtime = (Runtime *)self.bridge.runtime;
runtime->global().getPropertyAsFunction(*runtime, "jsCallSyncHook").call(*runtime, scrollView.contentOffset.y);
}
Copy the code
Define the global object jsCallSyncHook in the JS code, receive the offset value from Native, and process the business logic (only a few words are added here, but it can be more complex). Then call the runTest method of the previously bound HostObject nativeTest to continue the synchronous call.
global.jsCallSyncHook = function changeTxt(s) {
global.nativeTest.runTest('Offset is now'.+s);
};
Copy the code
Please set the C++ compiled version of Clang to C++11 or higher in Build Setting
The final result
Let’s put a breakpoint on the Native runTest and look at the call stack
It can be seen that the main thread has gone through the synchronous call process of Native->JS->Native, and it is done. Here’s what it looks like in the simulator
Note
This is just a simple attempt at JSI, and the code is a test code. If you want to take full advantage of JSI’s power, please wait for RN’s subsequent TurboModules and Fabric.
Reference
medium.com/@christian….
Github.com/ericlewis/r…
Til.hashrocket.com/posts/hxfbn…