上一篇有說到在有了JSI之後,JS和Native同時持有一個HostObject,那麼JS和Native之間就有了同步調用的基礎條件。
JS同步調用Native
實際上,在現在的RN(以0.59版本為例)中,已經實現了JS向Native代碼的同步調用,在iOS中,可以通過宏RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
來實現。
@implementation ConfigManager
RCT_EXPORT_MODULE();
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getApiUrl)
{
return API_URL;
}
@end
JS中的調用為
import { NativeModules } from 'react-native';
const apiUrl = NativeModules.ConfigManager.getApiUrl();
下面我們看一看RN是怎麼實現的,首先通過查看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中相應的調用為
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的同步調用。
Native同步調用JS
有了JSI,我們就可以完成Native向JS的同步調用,現在讓我們嘗試著實現上一篇中說到的ScrollView的onScroll的同步任務。
既然JS向Native的同步調用是通過nativeCallSyncHook
實現的,我們就來實現一個jsCallSyncHook
吧,從Native線程(包括主線程)能同步調用JS的runtime中的方法。
功能代碼
我們想要實現的是,滑動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;
}
導出HostObject到JS
需要實現兩個方法,第一個install()
是導出全局屬性nativeTest
到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));
}
第二個是轉出全局屬性的方法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的同步調用過程,大功告成。下面是模擬器裡的效果

Note
本篇只是JSI的簡單嘗試,代碼均為測試代碼,如果想充分利用JSI的強大功能,請靜候RN後續的TurboModules和Fabric。