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…