上一篇有说到在有了JSI以后,JS和Native同时持有一个HostObject,那么JS和Native之间就有了同步调用的基础条件。react
实际上,在如今的RN(以0.59版本为例)中,已经实现了JS向Native代码的同步调用,在iOS中,能够经过宏RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
来实现。git
@implementation ConfigManager
RCT_EXPORT_MODULE();
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getApiUrl)
{
return API_URL;
}
@end
复制代码
JS中的调用为github
import { NativeModules } from 'react-native';
const apiUrl = NativeModules.ConfigManager.getApiUrl();
复制代码
下面咱们看一看RN是怎么实现的,首先经过查看Native端的宏定义和源码,能够追溯到react-native
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); }));
复制代码
而后查看JS中相应的调用为api
function genMethod(moduleID: number, methodID: number, type: MethodType) {
if (type === 'promise') {
...
} else if (type === 'sync') {
fn = function(...args: Array<any>) {
...
return global.nativeCallSyncHook(moduleID, methodID, args);
};
}
...
}
复制代码
其实就是经过JSI,建立了nativeCallSyncHook
这个HostObject,实现了JS向Native的同步调用。promise
有了JSI,咱们就能够完成Native向JS的同步调用,如今让咱们尝试着实现上一篇中说到的ScrollView的onScroll的同步任务。bash
既然JS向Native的同步调用是经过nativeCallSyncHook
实现的,咱们就来实现一个jsCallSyncHook
吧,从Native线程(包括主线程)能同步调用JS的runtime中的方法。post
咱们想要实现的是,滑动ScrollView,将其offset传到JS端进行业务逻辑处理,而后同步更新当前页面的一个Label的text。更新Native页面的代码为:测试
int Test::runTest(Runtime& runtime, const Value& vl) {
// testView是包含UILabel和UIScrollView的UIView,lb即当前的UILabel
lb.text = [NSString stringWithUTF8String:vl.toString(runtime).utf8(runtime).c_str()];
[testView setNeedsLayout];
return 0;
}
复制代码
须要实现两个方法,第一个install()
是导出全局属性nativeTest
到JS的Runtime。ui
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));
}
复制代码
第二个是转出全局属性的方法runTest
。
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();
}
复制代码
而后须要在合适的地方(好比视图组件init的时候)进行binding,也就是调用下install()
。
auto test = std::make_unique<Test>();
std::shared_ptr<TestBinding> testBinding_ = std::make_shared<TestBinding>(std::move(test));
TestBinding::install(runtime, testBinding_);
复制代码
咱们在onScroll的时候调用JS的Runtime重的jsCallSyncHook
全局对象,将ScrollView的offset值传过去。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
Runtime* runtime = (Runtime *)self.bridge.runtime;
runtime->global().getPropertyAsFunction(*runtime, "jsCallSyncHook").call(*runtime, scrollView.contentOffset.y);
}
复制代码
在JS代码里定义jsCallSyncHook
这个全局对象,接收Native传过来的offset值,进行业务逻辑处理(这里仅是加了几个字,可是能够更复杂),而后调用以前已经绑定的nativeTest
这个HostObject的runTest
方法,继续完成同步调用。
global.jsCallSyncHook = function changeTxt(s) {
global.nativeTest.runTest('如今的offset是'+s);
};
复制代码
这里可能会遇到Native代码编译不过的问题,请在Build Setting中设置Clang的C++编译版本为C++11以上
咱们在Native的runTest
处打上断点看一下调用堆栈
能够看到在主线程通过了Native->JS->Native的同步调用过程,大功告成。下面是模拟器里的效果
本篇只是JSI的简单尝试,代码均为测试代码,若是想充分利用JSI的强大功能,请静候RN后续的TurboModules和Fabric。