React Native 源码解析之Initialize Nat
React Native 源码解析之Initialize Native Modules
上一篇的源码分析系列,我们讲解了RN初始化流程:RN代码的加载RCTJavaScriptLoader
,本文将继续跟进RN初始化流程中的重要一环:Initialize Native Modules,直接入主题。
上文中我们了解到:[self.batchedBridge start]:初始化js/OC交互所需要的完整的环境配置,入口便从这里开始,找到如下代码段:
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
下面深入分析其中的代码:
注册代理配置的RNModule
代理中可以自己实现预先加载自己的RNModule,不过目前尚且不太懂什么场景需要实现这个接口,有了解的麻烦回复一下告知一下,谢谢。
NSArray<id<RCTBridgeModule>> *extraModules = nil;
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
extraModules = [self.delegate extraModulesForBridge:_parentBridge]; }
} else if (self.moduleProvider) {
extraModules = self.moduleProvider();
}
注册全局其他的RNModule
// The executor is a bridge module, but we want it to be instantiated before
// any other module has access to the bridge, in case they need the JS thread.
// TODO: once we have more fine-grained control of init (t11106126) we can
// probably just replace this with [self moduleForClass:self.executorClass]
RCT_PROFILE_BEGIN_EVENT(0, @"JavaScriptExecutor", nil);
if (!_javaScriptExecutor) {
id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
bridge:self];
moduleDataByName[moduleData.name] = moduleData;
[moduleClassesByID addObject:self.executorClass];
[moduleDataByID addObject:moduleData];
executorModule.delegate = self;
// NOTE: _javaScriptExecutor is a weak reference
_javaScriptExecutor = executorModule;
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
// Set up moduleData for automatically-exported modules
RCT_PROFILE_BEGIN_EVENT(0, @"ModuleData", nil);
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
// Check for module name collisions
RCTModuleData *moduleData = moduleDataByName[moduleName];
if (moduleData) {
if (moduleData.hasInstance) {
// 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
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass, moduleName, moduleData.moduleClass);
}
}
// Instantiate moduleData (TODO: can we defer this until config generation?)
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
为什么需要先初始化一个全局的javaScriptExecutor
此处暂时不做详细分析。
外部的RNModule是如何注册进来的呢?实际上RCTModuleClasses全局的数组记录了native注册的NativeModule,以下方法会触发注册:
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];
}
以下是注册RNModule必须加入的语句,这也解释了NativeModule是如何注册到全局的配置列表的。
/**
* 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); }
初始化单个的RNModule
上述讲了注册RNModule的列表,下面开始看一下单个RNModule的实体对象类:RCTModuleData
的初始化流程,
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
bridge:self];
moduleDataByName[moduleData.name] = moduleData;
[moduleClassesByID addObject:self.executorClass];
[moduleDataByID addObject:moduleData];
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
_instance = instance;
_moduleClass = [instance class];
[self setUp];
}
return self;
}
看到上述代码首先让我猜测_bridge = bridge;_instance = instance;
的实现才可以让所有的RNModule都可以获取到全局的bridge
,
_instance
是@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
是RCTModuleData
持有的一个实现了RCTBridgeModule
协议的实例,即一个RNModule,而我们如何把RCTModuleData
持有的bridge传给_instance
的,我们看看下面一段代码:
- (void)setBridgeForInstance
{
if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setBridgeForInstance]", nil);
@try {
[(id)_instance setValue:_bridge forKey:@"bridge"];// 访问非public的方法
}
@catch (NSException *exception) {
RCTLogError(@"%@ has no setter or ivar for its bridge, which is not "
"permitted. You must either @synthesize the bridge property, "
"or provide your own setter method.", self.name);
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
}
利用了KVC
实现了,因次也解释了为什么自定义RNModule一般需要加入@synthesize bridge = _bridge;
。
完成ConstantsToExport的配置
我们在构建一个NativeModule可以加入类似如下代码导出native的相关配置给js:
- (NSDictionary<NSString *, id> *)constantsToExport
{
UIDevice *device = [UIDevice currentDevice];
return @{
@"forceTouchAvailable": @(RCTForceTouchAvailable()),
@"osVersion": [device systemVersion],
@"interfaceIdiom": interfaceIdiom([device userInterfaceIdiom]),
};
}
这一段是如何实现的呢?
[self prepareModulesWithDispatchGroup:dispatchGroup];
// Set up modules that require main thread init or constants export
for (RCTModuleData *moduleData in _moduleDataByID) {
if (whitelistedModules && ![whitelistedModules containsObject:[moduleData moduleClass]]) {
continue;
}
if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) {
// Modules that need to be set up on the main thread cannot be initialized
// lazily when required without doing a dispatch_sync to the main thread,
// which can result in deadlock. To avoid this, we initialize all of these
// modules on the main thread in parallel with loading the JS code, so
// they will already be available before they are ever required.
dispatch_block_t block = ^{
if (self.valid) {
[self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread];
(void)[moduleData instance];
[moduleData gatherConstants]; // 注册此处核心代码
[self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
}
};
if (initializeImmediately && RCTIsMainQueue()) {
block();
} else {
// We've already checked that dispatchGroup is non-null, but this satisifies the
// Xcode analyzer
if (dispatchGroup) {
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
}
}
_modulesInitializedOnMainQueue++;
}
}
- (void)gatherConstants
{
if (_hasConstantsToExport && !_constantsToExport) {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil);
(void)[self instance];
if (!RCTIsMainQueue()) {
RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
RCTExecuteOnMainThread(^{
self->_constantsToExport = [self->_instance constantsToExport] ?: @{};
}, YES);
#pragma clang diagnostic pop
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
}
我们来看看结果:
{
forceTouchAvailable = 0;
interfaceIdiom = phone;
osVersion = "10.0";
}
js端的使用:Platform.ios.js
'use strict';
var Platform = {
OS: 'ios',
get Version() {
return require('NativeModules').IOSConstants.osVersion;
},
select: (obj: Object) => obj.ios,
};
module.exports = Platform;
本文先讲到这里,下文继续从这里开始讲解具体js端的页面是如何渲染的,js的方法调用时如何走到native对应的NativeModule中的。