上一篇有說到在有了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。