[ReactNative翻译]React Native JSI 挑战赛 2

227 阅读4分钟

本文由 简悦 SimpRead转码, 原文地址 medium.com

自从我写了 @ericlewis I 和做了什么来让 JSI 在 React Native 0.59 中工作之后,我一直在努力......。

自从我撰文讲述了@ericlewis我是如何让 JSI 在 React Native 0.59 中工作的,我就一直在开发我的导航和过渡库Fluid Transitions的下一个版本,该库将使用React Native Reanimated作为其动画引擎(实际上,我正在编写的新库能够同时使用 React Native Animated 和 Reanimated - 以防万一)。

下一版的 Fluid Transitions 是围绕一个基础组件构建的,它会自动插值所有样式变化。这将导致创建大量的小动画,其中有些动画可能很复杂(请尝试添加颜色插值、变换并使用弹簧运行它们,您就会明白:)。

Background

React Native Reanimated 使用桥接器来序列化动画信息,这是有代价的--组合复杂动画的速度可能比使用 React Native Animated(使用/不使用原生驱动程序)要慢--这都是因为通过桥接器进行序列化的缘故。

我决定看看能否在 React Native Reanimated 中引入 JSI 来加快速度。该库推送描述动画和表达式的节点,并在 React Native UIManager 准备就绪时对它们进行评估。这是通过将信息添加到队列,并在 UIManager 准备就绪时执行队列中的操作来实现的--这似乎可以被 JSI 绑定所利用。

Implementation

我首先克隆了 React Native Reanimated 软件源,并在 XCode 中添加了所有必要的编译器和链接器设置(如我们的 commits 中所述)--基本上就是设置一些链接器标志和修复头文件搜索路径,以便能够使用动态 JSI 接口。

我的想法是创建一个与本地模块并存的 JSI 模块,并使用相同的功能--但这次不需要过桥。

The binding object

正如我们在上一篇关于 JSI 的文章中所看到的,我们需要创建一个绑定类,将 JavaScript 调用转换为 C++/Objective-C 调用。我的绑定类是这样的(REAJsiModule.mm)

class JSI_EXPORT REAJsiModule : public jsi::HostObject {
public:
  REAJsiModule(REAModule* reaModule);
  static void install(REAModule *reaModule);
  jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name)   
    override;
private:
  REAModule* reamodule_;
};

注意:为了避免过多的 gists,所有代码片段都将以内联代码显示--最终代码请参考底部的链接。

这个类是做什么的?这是一个简单的 JSI 绑定类,它封装了 REAModule(Reanimated 中 JavaScript 与本机之间的通信通道,感兴趣的可以去look看看)中的调用。

静态的 install 函数将在当前桥上安装 JSI 绑定,而 get 方法将返回我们所有的函数封装器(参考资料和解释请参见上一篇 挑战)。

要安装该类,我们可以扩展 REAModule(NativeModule)并执行安装:

- (void)setBridge:(RCTBridge *)bridge
{
 [super setBridge:bridge];
...
 
 REAJsiModule::install(self);
}

还记得上一篇文章中我们在安装 JSI 绑定时遇到的问题吗?当我们在原生模块中时,这个问题就简单多了--在 setBridge 方法中就有一个桥!在我们的绑定中,安装也非常简单:

RCTCxxBridge *cxxBridge = (RCTCxxBridge *)reaModule.bridge;
if (cxxBridge.runtime == nullptr) {
 return;
}
jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime;
auto reaModuleName = “Reanimated”;
auto reaJsiModule = std::make_shared<REAJsiModule>(std::move(reaModule));
auto object = jsi::Object::createFromHostObject(runtime, reaJsiModule);
runtime.global().setProperty(runtime, reaModuleName, std::move(object));

请注意,我们在安装前会检查运行时是否为空--这正是安装 JSI 绑定的问题所在--运行时可能并非始终可用--尤其是在 VSCode 内通过 Chrome 浏览器调试时。代码的其余部分只是在桥上公开绑定的胶水。

向 JavaScript 公开函数是在 get 函数中完成的:

if (methodName == “dropNode”) {
  REAModule* reamodule = reamodule_;
  return jsi::Function::createFromHostFunction(runtime, name, 1,   
    [reamodule](jsi::Runtime &runtime,
                const jsi::Value &thisValue,
                const jsi::Value *arguments,
                size_t count) -> jsi::Value {
   auto arg1 = &arguments[0];
   [reamodule dropNode:
     [NSNumber numberWithDouble:arg1->asNumber()]];
   return jsi::Value::undefined();
 });
}

Threads and queues

一切似乎都很顺利--但我在 XCode 中遇到了一些奇怪的崩溃,每当我看到这样的情况,我就会想到线程问题。检查哪些线程在调用哪些函数并不难。你可以破解本地代码并检查线程面板(记住,在 VSCode/Chrome 中调试 JS 时,我们会看到旧的本地模块行为,从而看到哪个函数在哪个线程上。

我注意到,使用旧的原生模块调用的内容是在 "UIManager "线程上调用的--我使用了 React Native 的 "RCTExecuteOnUIManagerQueue "函数,以确保它们都是在同一个线程上调用的--无论我们调用的是 JSI 还是原生模块的函数。

那么最大的问题是--这对性能有帮助吗?是的,有一点......它并没有我希望的那么快--虽然感觉更敏捷了,但运行动画时仍会出现一些延迟。我做了一些测试,发现使用 JSI 要比使用桥接器快 4-5 倍左右。

我还没有时间尝试在安卓系统中做同样的事情,但希望有人能帮忙--这应该是可行的,但需要一些时间来解决我们在 iOS 系统中已经解决的所有构建问题。因此,这项任务仍有待完成!

React Native Reanimatedgithub.com/kmagiera/re…

JSI 分支/分叉github.com/chrfalch/re…

Fluid Transitions(当前版本)github.com/fram-x/Flui…