JSI小試牛刀——Native同步調用JS代碼

NO IMAGE

上一篇有說到在有了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處打上斷點看一下調用堆棧

JSI小試牛刀——Native同步調用JS代碼

可以看到在主線程經過了Native->JS->Native的同步調用過程,大功告成。下面是模擬器裡的效果

JSI小試牛刀——Native同步調用JS代碼

Note

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

Reference

medium.com/@christian.…

github.com/ericlewis/r…

til.hashrocket.com/posts/hxfbn…

相關文章

RocketMQ源碼分析之路由中心(NameServer)

一文理解Netty模型架構

當Kotlin愛上React,會發生什麼反應

Gin(九):生成restful接口