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

上一篇有說到在有了JSI以後,JS和Native同時持有一個HostObject,那麼JS和Native之間就有了同步調用的基礎條件。react

JS同步調用Native

實際上,在如今的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

Native同步調用JS

有了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;
}
複製代碼

導出HostObject到JS

須要實現兩個方法,第一個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處打上斷點看一下調用堆棧

JSI-sync-call

能夠看到在主線程通過了Native->JS->Native的同步調用過程,大功告成。下面是模擬器裏的效果

JSI-demo.gif

Note

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

Reference

medium.com/@christian.…

github.com/ericlewis/r…

til.hashrocket.com/posts/hxfbn…

相關文章
相關標籤/搜索