RN:ReactNative 简称,文章中后续使用RN进行代替全称 NA:Native的简称
1. Native ModuleRegistry 是如何收集
先看例子
1.1 RN Module - Networking
/**
* Bridge module that provides the JS interface to the network stack.
*/
@implementation RCTNetworking
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(sendRequest
: (JS::NativeNetworkingIOS::SpecSendRequestQuery &)query callback
: (RCTResponseSenderBlock)responseSender)
{
....
}
RCT_EXPORT_METHOD(abortRequest : (double)requestID)
{
dispatch_async([self requestQueue], ^{
[self->_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
[self->_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
});
}
RCT_EXPORT_METHOD(clearCookies : (RCTResponseSenderBlock)responseSender)
{
dispatch_async([self requestQueue], ^{
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
if (!storage.cookies.count) {
responseSender(@[ @NO ]);
return;
}
for (NSHTTPCookie *cookie in storage.cookies) {
[storage deleteCookie:cookie];
}
responseSender(@[ @YES ]);
});
}
@end
阅读上述代码看见两个关键的宏定义,RCT_EXPORT_MODULE/RCT_EXPORT_METHOD,字面理解到处Networking模块,以及到处对应方法,提供给JS侧使用。那么我们来看看这两段宏定义分别有什么用处。
1.2 RCT_EXPORT_MODULE
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_*name argument*
* *will be used as the JS module name. If omitted, the JS module name will*
* *match the Objective-C class name.*
*/
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
} \
+(void)load \
{ \
RCTRegisterModule(self); \
}
-
1.1.1 两个static方法,
-
1.1.2 js_name 标记ModuleName,
-
1.1.3 load函数启动调用 RCTRegisterModule 方法
每个export_module会在启动阶段主动调用RCTRegisterModule方法,接下来看下RCTRegisterModule实现
1.3 RCTRegisterModule
static NSMutableArray<Class> *RCTModuleClasses;
static dispatch_queue_t RCTModuleClassesSyncQueue;
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
* **TODO: (T115656171) Refactor RCTRegisterModule out of Bridge.m since it doesn't use the Bridge.**
*/
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
RCTModuleClassesSyncQueue =
dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
});
RCTAssert(
[moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
[RCTModuleClasses addObject:moduleClass];
});
}
NSMutableArray *RCTModuleClasses 中添加对应的ModuleClass
RN在启动app完成后,收集了所有的Module的Class存储到数组中。
上述代码中我们可以看出RN在App启动的时候做轻量级别的信息收集,那么在RN-Bridge启动的时候做了什么操作呢,是否完成了 ModuleRegistry 生成以及处理呢?
1.4 Bridge Start
1.4.1 先看Bridge.start
代表RN开始初始化。代码中截取了想要表达部分,_initializeModules: withDispatchGroup: lazilyDiscovered 方法,
@implementation RCTCxxBridge
- (void)start {
/// 截取关键代码
// Initialize all native modules that cannot be loaded lazily
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
}
@end
1.4.2 再看Modules的初始化
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
// Set up moduleData for automatically-exported modules
NSArray<RCTModuleData *> *moduleDataById = [self _registerModulesForClasses:modules lazilyDiscovered:lazilyDiscovered];
if (lazilyDiscovered) {
} else {
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
(void)[moduleData instance];
}
}
}
return moduleDataById;
}
// ModuleId 其实是一个递增的int值
namespace {
int32_t getUniqueId()
{
static std::atomic<int32_t> counter{0};
return counter++;
}
}
- (id<RCTBridgeModule>)instance
{
NSString *moduleName = [self name];
int32_t requestId = getUniqueId();
BridgeNativeModulePerfLogger::moduleCreateStart([moduleName UTF8String], requestId);
if (!_setupComplete) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass]), nil);
if (_requiresMainQueueSetup) {
// The chances of deadlock here are low, because module init very rarely
// calls out to other threads, however we can't control when a module might
// get accessed by client code during bridge setup, and a very low risk of
// deadlock is better than a fairly high risk of an assertion being thrown.
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData instance] main thread setup", nil);
if (!RCTIsMainQueue()) {
RCTLogWarn(@"RCTBridge required dispatch_sync to load %@. This may lead to deadlocks", _moduleClass);
}
RCTUnsafeExecuteOnMainQueueSync(^{
[self setUpInstanceAndBridge:requestId];
});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
} else {
[self setUpInstanceAndBridge:requestId];
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
} else {
BridgeNativeModulePerfLogger::moduleCreateCacheHit([moduleName UTF8String], requestId);
}
if (_instance) {
BridgeNativeModulePerfLogger::moduleCreateEnd([moduleName UTF8String], requestId);
} else {
BridgeNativeModulePerfLogger::moduleCreateFail([moduleName UTF8String], requestId);
}
return _instance;
}
- NSArray<Class> 转换成 NSArray<RCTModuleDatas> ,在上述 _initializeModules 过程中调用 (void)[moduleData instance]; 完成Module在Native的初始化注册。
- ModuleId 其实就是一个递增int值~
- (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses
lazilyDiscovered:(BOOL)lazilyDiscovered {
NSArray *moduleClassesCopy = [moduleClasses copy];
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
for (Class moduleClass in moduleClassesCopy) {
if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
continue;
}
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
// Check for module name collisions
RCTModuleData *moduleData = _moduleDataByName[moduleName];
if (moduleData) {
if (moduleData.hasInstance || lazilyDiscovered) {
// Existing module was preregistered, so it takes precedence
continue;
} else if ([moduleClass new] == nil) {
// The new module returned nil from init, so use the old module
continue;
} else if ([moduleData.moduleClass new] != nil) {
// Both modules were non-nil, so it's unclear which should take precedence
RCTLogWarn(
@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass,
moduleName,
moduleData.moduleClass);
}
}
// Instantiate moduleData
// TODO #13258411: can we defer this until config generation?
int32_t moduleDataId = getUniqueId();
BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId);
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self
moduleRegistry:_objCModuleRegistry
viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED
bundleManager:_bundleManager
callableJSModules:_callableJSModules];
BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId);
// Highlight 添加到_moduleDataByID当中
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
[_moduleDataByID addObjectsFromArray:moduleDataByID];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return moduleDataByID;
}
上述代码运行完毕后ModuleRegistry在RN启动后RN的初始化代码已经结束,读者们会有好奇并没有介绍 ModuleRegistry有类似的[addmodule forName]的操作,真的注册了么 ?接着往下看。(我第一次看的时候也是有这个疑惑)
1.5 RCTModuleRegistry 实现
1.5.1 先看实现源码
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridge.h"
#import "RCTTurboModuleRegistry.h"
@class RCTBridgeModule;
@implementation RCTModuleRegistry {
__weak id<RCTTurboModuleRegistry> _turboModuleRegistry;
__weak RCTBridge *_bridge;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)setTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry
{
_turboModuleRegistry = turboModuleRegistry;
}
- (id)moduleForName:(const char *)moduleName
{
return [self moduleForName:moduleName lazilyLoadIfNecessary:YES];
}
- (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
id<RCTBridgeModule> module = nil;
RCTBridge *bridge = _bridge;
if (bridge) {
module = [bridge moduleForName:[NSString stringWithUTF8String:moduleName] lazilyLoadIfNecessary:lazilyLoad];
}
id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
if (module == nil && turboModuleRegistry && (lazilyLoad || [turboModuleRegistry moduleIsInitialized:moduleName])) {
module = [turboModuleRegistry moduleForName:moduleName];
}
return module;
}
- (BOOL)moduleIsInitialized:(Class)moduleClass
{
RCTBridge *bridge = _bridge;
if (bridge) {
return [bridge moduleIsInitialized:moduleClass];
}
id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
if (turboModuleRegistry) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
return [turboModuleRegistry moduleIsInitialized:[moduleName UTF8String]];
}
return NO;
}
@end
从代码中我们可以看出,RCTModuleRegistry并没有真正的存储Module这些内容,而是从brdige中获取对应的数据和标记。充当了一个中转站,因此我们需要去了解bridge中是如何实现这些的。(思考:为什么不直接在RCTModuleRegistry中做存储和逻辑实现呢?)
1.5.2 RCTCxxBridge中的方法实现
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
if (RCTTurboModuleEnabled() && _turboModuleRegistry) {
const char *moduleNameCStr = [moduleName UTF8String];
if (lazilyLoad || [_turboModuleRegistry moduleIsInitialized:moduleNameCStr]) {
id<RCTTurboModule> module = [_turboModuleRegistry moduleForName:moduleNameCStr warnOnLookupFailure:NO];
if (module != nil) {
return module;
}
}
}
if (!lazilyLoad) {
return _moduleDataByName[moduleName].instance;
}
RCTModuleData *moduleData = _moduleDataByName[moduleName];
if (moduleData) {
if (![moduleData isKindOfClass:[RCTModuleData class]]) {
// There is rare race condition where the data stored in the dictionary
// may have been deallocated, which means the module instance is no longer
// usable.
return nil;
}
return moduleData.instance;
}
// Module may not be loaded yet, so attempt to force load it here.
// Do this only if the bridge is still valid.
if (_didInvalidate) {
return nil;
}
const BOOL result = [self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] &&
[self.delegate bridge:self didNotFindModule:moduleName];
if (result) {
// Try again.
moduleData = _moduleDataByName[moduleName];
#if RCT_DEV
// If the `_moduleDataByName` is nil, it must have been cleared by the reload.
} else if (_moduleDataByName != nil) {
RCTLogError(@"Unable to find module for %@", moduleName);
}
#else
} else {
RCTLogError(@"Unable to find module for %@", moduleName);
}
#endif
return moduleData.instance;
}
- TurboModule相关逻辑处理
- Lazy相关判断处理
- delegate 转发获取兜底Module
总结上述: 本地RCTModuleRegistry维护依赖于_moduleDataByName属性进行维护,其中根据moduleName为key,RCTModuleData为Value的Map。RCTModuleData中有对应Module中所有的属性这些。
好像和我们认知的RCTModuleRegistry映射到JS这段逻辑没有?启动也没有主动做这个事情?答案肯定是有做的。只不过目前这个映射是懒加载,触发发生在JS侧。接下来为大家介绍,是如何映射的。
2.来看映射
在上述我们提及到NA是如何声明Module的逻辑,我们先来看下JS侧是如何调对应Module代码的逻辑使用的。
2.1 JS调用
import React, { useState, useEffect } from 'react';
import {View, Text, Button, NativeModules} from 'react-native';
const {MyNativeModule} = NativeModules;
const NativeModuleExample = () => {
const onPressButton = () => {
MyNativeModule.showNativeAlert(
'Hello from React Native!',
(error, result) => {
if (error) {
console.error(error);
} else {
console.log(result);
}
},
);
};
return <Button title="Show Native Alert" onPress={onPressButton} />;
};
export default NativeModuleExample;
直接看TS代码,我们看不出MyNativeModule是如何生成的,只能感知到,NativeModules中存在该Module,那么我们执行tsc命令,看下js的关键代码
var MyNativeModule = exports.NativeModules.MyNativeModule;
该代码就很清晰,MyNativeModule是由 NativeModules.MyNativeModule 点语法获取到对应的模块。那么移步到NativeModules.d.ts(对NativeModules.js的对外声明),因此我们可以直接看NativeModule.js
let NativeModules: {[moduleName: string]: $FlowFixMe, ...} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else {
const bridgeConfig = global.__fbBatchedBridgeConfig;
invariant(
bridgeConfig,
'__fbBatchedBridgeConfig is not set, cannot invoke native modules',
);
const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
(bridgeConfig.remoteModuleConfig || []).forEach(
(config: ModuleConfig, moduleID: number) => {
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info) {
return;
}
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID),
});
}
},
);
}
module.exports = NativeModules;
存在两个分支:
- NativeModules = global.nativeModuleProxy;
- bridgeConfig.remoteModuleConfig 获取所有配置
目前我的代码中是走到了分支1,nativeModuleProxy。那么我们仔细看下nativeModuleProxy这个东西是个什么东西?
2.2 nativeModuleProxy
void JSIExecutor::initializeRuntime() {
SystraceSection s("JSIExecutor::initializeRuntime");
bindNativePerformanceNow(*runtime_);
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
}
好家伙,原来是个HostObject注入对象呀。NativeModules.MyNativeModule无非就是调用了NativeModuleProxy中的get方法。
2.3 NativeModuleProxy
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
public:
NativeModuleProxy(std::shared_ptr<JSINativeModules> nativeModules)
: weakNativeModules_(nativeModules) {}
Value get(Runtime& rt, const PropNameID& name) override {
if (name.utf8(rt) == "name") {
return jsi::String::createFromAscii(rt, "NativeModules");
}
auto nativeModules = weakNativeModules_.lock();
if (!nativeModules) {
return nullptr;
}
return nativeModules->getModule(rt, name);
}
void set(Runtime&, const PropNameID&, const Value&) override {
throw std::runtime_error(
"Unable to put on NativeModules: Operation unsupported");
}
private:
std::weak_ptr<JSINativeModules> weakNativeModules_;
};
清晰可见重载了方法 Value get(Runtime& rt, const PropNameID& name) override,最终调用到nativeModules->getModule(rt, name);返回value。
2.4 JSINativeModules::getModule
Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
if (!m_moduleRegistry) {
return nullptr;
}
std::string moduleName = name.utf8(rt);
const auto it = m_objects.find(moduleName);
if (it != m_objects.end()) {
return Value(rt, it->second);
}
auto module = createModule(rt, moduleName);
if (!module.has_value()) {
return nullptr;
}
auto result =
m_objects.emplace(std::move(moduleName), std::move(*module)).first;
Value ret = Value(rt, result->second);
return ret;
}
2.5 懒加载对应的Module
std::optional<Object> JSINativeModules::createModule(
Runtime& rt,
const std::string& name) {
if (!m_genNativeModuleJS) {
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
auto result = m_moduleRegistry->getConfig(name);
if (!result.has_value()) {
return std::nullopt;
}
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";
CHECK(moduleInfo.isObject())
<< "Module returned from genNativeModule isn't an Object";
std::optional<Object> module(
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
return module;
}
- m_genNativeModuleJS nativeModule同步info的对象,全局属性中获取
- moduleInfo 生成获取
2.6 m_genNativeModuleJS
m_genNativeModuleJS = rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");是从JSRuntime中获取的全局属性,那么我们看下这一个全局属性是啥。
// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{
name: string,
module?: {...},
...
} {
if (!config) {
return null;
}
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
invariant(
!moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
"Module name prefixes should've been stripped by the native side " +
"but wasn't for " +
moduleName,
);
if (!constants && !methods) {
// Module contents will be filled in lazily later
return {name: moduleName};
}
const module: {[string]: mixed} = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
(promiseMethods && arrayContains(promiseMethods, methodID)) || false;
const isSync =
(syncMethods && arrayContains(syncMethods, methodID)) || false;
invariant(
!isPromise || !isSync,
'Cannot have a method that is both async and a sync hook',
);
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
} else {
console.warn(
`Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
);
}
if (__DEV__) {
BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
}
return {name: moduleName, module};
}
最终调用genModule完成NativeModule映射到JS侧的功能。
3.总结
- NA对本地Class收集
- NA启动Bridge,完成ModuleData的收集
- JS侧调用懒加载同步ModuleRegistry配置信息