rn-relates
rn-relates copied to clipboard
Reanimated接入问题
背景
最近准备在 App 中接入 Reanimated 和 GestureHandler,之前其实尝试接过,不过有未知报错,后续因为有业务跟进,便一再搁置。前段时间领导提到动效库的必要性,考虑到这两个库的高性能特性,因此再次尝试接入。
接入
babel-preset缺失
首次接入时,两者版本分别是 [email protected]、[email protected],基本属于最新稳定版本。参照官方文档的 安装步骤,在项目根目录添加了 babel.config.js 配置文件,并补充相应配置:
module.exports = {
plugins: ['react-native-reanimated/plugin']
};
通过打进 XRN 的方式在工程中引用,并新建相应的测试页面,发现启动时报错:
error: SyntaxError: /XPMotors_ReactNative/node_modules/react-native/index.js: Unexpected token, expected "{" (13:7)
想到可能是因为新增 babel 的配置文件导致语法解析问题,因此上 RN 官网找到 0.62.x 版本对应的配置内容,补充后的内容:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin']
};
ReanimatedModule缺失
清除缓存重新运行 yarn start --reset-cache,发现依然报错:
TypeError: undefined is not an object(evaluating 'this.InnerNativeModule.installCoreFunctions')
单纯报错信息上看,InnerNativeModule 为 undefined 了,从红屏的源码调用栈来看,最终找到 NativeReanimated.ts :
export class NativeReanimated {
native: boolean;
private InnerNativeModule: any;
constructor(native = true) {
// 1 检测模块代理是否存在,不存在则手动进行初始化
if (global.__reanimatedModuleProxy === undefined) {
const { ReanimatedModule } = NativeModules;
ReanimatedModule?.installTurboModule();
}
// 2 赋值 InnerNativeModule
this.InnerNativeModule = global.__reanimatedModuleProxy;
this.native = native;
}
installCoreFunctions(valueSetter: <T>(value: T) => void): void {
// 出错位置,this.InnerNativeModule 为 undefined
return this.InnerNativeModule.installCoreFunctions(valueSetter);
}
...
}
假设 global.__reanimatedModuleProxy 为空,那么通过 ReanimatedModule?.installTurboModule() 的方式手动赋值,继续搜索 installTurboModule 方法:
RCT_EXPORT_METHOD(installTurboModule)
{
// TODO: Move initialization from UIResponder+Reanimated to here
}
是个空实现!继续找到 UIResponder+Reanimated.mm:
#ifndef DONT_AUTOINSTALL_REANIMATED
@implementation UIResponder (Reanimated)
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
const auto installer = reanimated::REAJSIExecutorRuntimeInstaller(bridge, NULL);
#if RNVERSION >= 64
// installs globals such as console, nativePerformanceNow, etc.
return std::make_unique<ExecutorFactory>(RCTJSIExecutorRuntimeInstaller(installer));
#else
return std::make_unique<ExecutorFactory>(installer);
#endif
}
@end
#endif
到这里发现有一个宏标记 DONT_AUTOINSTALL_REANIMATED,从命名上基本就能知道他是干嘛的了,所以100%可以确定 Reanimate 具备自动初始化的能力,那么初始化方法 jsExecutorFactoryForBridge 到底是怎么调用的?
实际上 jsExecutorFactoryForBridge 来自 RN 官方的 RCTCxxBridgeDelegate 协议,查看官方解释:
@protocol RCTCxxBridgeDelegate <RCTBridgeDelegate>
/**
* In the RCTCxxBridge, if this method is implemented, return a
* ExecutorFactory instance which can be used to create the executor.
* If not implemented, or returns an empty pointer, JSIExecutorFactory
* will be used with a JSCRuntime.
*/
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge;
@end
因此可以知道,Reanimated 团队通过实现该协议,实现了自己的 JSI 运行时实例,即是官方提到的 Separate JavaScript VM。找到 RN 官方调用位置:
// RCTCxxBridge.mm
- (void)start
{
...
// Prepare executor factory (shared_ptr for copy into block)
std::shared_ptr<JSExecutorFactory> executorFactory;
if (!self.executorClass) {
if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
}
...
}
}
所以 Reanimated 团队用了比较 hack 的方式,在 RN 初始化 Bridge 实例的过程中,来达到自动初始化 ReanimatedModule 的目的。
XRNManager背锅
通过以上源码,基本梳理清楚了 Reanimated 的整个初始化过程。但是不禁有疑问,为何会导致自动初始化失败?到底是什么环节导致问题?实际问题就出现在 UIResponder+Reanimated 和 RCTCxxBridge#delegate 上:
- 首先一般的 RN 工程中,RCTBridge 都是在 AppDelegate 中进行初始化,此时 AppDelegate 作为 RCTBridge 的 delegate;同时 AppDelegate 是 UIResponder 的子类
- 由于
UIResponder+Reanimated分类实现了对应的协议方法,实际上已经在运行时中为 UIResponder 的 Metaclass 添加了jsExecutorFactoryForBridge方法 - RN 初始化的时候,
[cxxDelegate jsExecutorFactoryForBridge:self]实际就是[aAppDelegate jsExecutorFactoryForBridge:self]
目前 XRN 提供了适配层,整个 RN 运行环境的初始化工作都由 XRNManager 完成,而 XRNManager 是 NSObject 的子类,并非继承自 UIResponder,因此 [self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)] 返回结果其实是 NO,也就无法完成后续的初始化逻辑了。
// XRNManager.h
@interface XRNManager : NSObject<RCTBridgeDelegate>
@end
解决方案
修改 XRNManager 继承自 UIResponder 即可。重新打包 XRN 后,验证通过。
参考资料
- Simplify the iOS installation process and turn off turbo modules for other modules than Reanimated.
- TypeError: undefined is not an object (evaluating 'InnerNativeModule.installCoreFunctions')
- Reanimated module is not initialized when a class that implements the RCTBridgeDelegate Protocol is used
- 深入理解Objective-C:Category
🐂🍺
你要不要给原库提个PR?目前2.9.1还有这个问题。我是把 UIResponder+Reanimated 改继承自 NSObject 修成功了。但原理上并不明白与你说的 XRNManager : NSObject 有什么区别
来自:https://github.com/software-mansion/react-native-reanimated/issues/2791#issuecomment-1004779673
@dubiao 不知官方设计 UIResponder+Reanimated 的初衷是不是因为 AppDelegate 是 UIResponder 的子类,正常来说改用 NSObject 分类的话就一劳永逸。XRNManager 也是 NSObject 的子类,肯定是无法查找到 UIResponder+Reanimated 里面的方法的