rn应用启动时,利用RCTBundleURLProvider获取到js端入口文件index.ios.js的url地址,再生成程序根视图RCTRootView。RCTRootView的创建方法
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
moduleProvider:nil
launchOptions:launchOptions];
return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
生成一个bridge,在调用initWithBridge方法,在这个方法中注册一系列通知,用来监听js文件的加载过程,在文件加载完成后刷新视图等。RCTRootView内部有属性强引用bridge对象。
bridge的创建方法主要调用的是如下
- (void)setUp
{
... 省略
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
}
bridge没做啥实事,主要作用就是生成了RCTBatchedBridge对象,这是RCTBridge的一个子类,并强引用了它。
具体干活的对象是RCTBatchedBridge。看它的start方法。
- (void)start
{
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge userInfo:@{@"bridge": self}];
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge setUp]", nil);
dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
---------- 标记@1
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSData *sourceCode;
---------- 标记@2
[self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
if (error) {
RCTLogWarn(@"Failed to load source: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
} onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
[loadingView updateProgress:progressData];
#endif
}];
---------- 标记@3
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
RCTPerformanceLogger *performanceLogger = self->_performanceLogger;
__block NSString *config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
---------- 标记@4
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
---------- 标记@5
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
[weakSelf setUpExecutor];
[performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
});
---------- 标记@6
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if (weakSelf.valid) {
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge moduleConfig", nil);
[performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
config = [weakSelf moduleConfig];
[performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
});
---------- 标记@7
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
[performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
if (error) {
RCTLogWarn(@"Failed to inject config: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
---------- 标记@8
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode];
}
});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
@1: 创建外层的队列组A
@2: 队列组A中的第一个任务,加载我们写的那些js源码文件
@3: 同步的形式,将OC中通过宏RCT_EXPORT_MODULE导出的所有模块信息,例如模块名字、模块对应的RCTModuleData实例等添加到属性_moduleDataByID等之中。同时初始化所有不能懒加载的模块实例。
@4: 创建队列组B,该队列组用于A的一个子任务之中。意思就是队列组A有一个任务,这个任务内部又创建了一个队列组B,B队列组又包含了两个任务,分别是@5和@6。
@5: 队列组B的子任务,初始化js执行器_javaScriptExecutor。
@6: 将@3已经装载的模块信息,按照一定格式组装结构。
@7: @5、@6完成,将模块信息注入js执行上下文中,这样js代码就可以调用了。
@8: @5、@6完成后,未等@7完成,队列组即已经退出,执行@8操作。执行上面加载的js代码,通知RootView页面刷新等。
细究部分:
@3: initModulesWithDispatchGroup方法的实现
- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
... 抽取主要代码
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
RCTModuleData *moduleData = moduleDataByName[moduleName];
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
_moduleDataByID = [moduleDataByID copy];
_moduleDataByName = [moduleDataByName copy];
_moduleClassesByID = [moduleClassesByID copy];
[self prepareModulesWithDispatchGroup:dispatchGroup];
}
方法RCTGetModuleClasses()拿到所有OC中通过RCT_EXPORT_MODULE(js_name)导出的模块名字(若宏后面没写名字,则默认为文件名字),遍历,包装成RCTModuleData类型的数据,存到RCTBatchedBridge的三个属性_moduleDataByID、_moduleDataByName、_moduleClassesByID之中。
@3内部方法prepareModulesWithDispatchGroup实现
- (void)prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
... 抽取主要代码
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) {
dispatch_block_t block = ^{
if (self.valid) {
[self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread];
(void)[moduleData instance]; --------- 标记p
[moduleData gatherConstants]; --------- 标记q
[self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
}
};
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
}}
意思是找出所有模块中需要预先实例化的模块,初始化它。具体是如果当前模块实现了init或者constantsToExport方法,则上面属性requiresMainQueueSetup和hasConstantsToExport属性会为YES,进入标记p和标记q处,实例化模块。
一般模块采用的是懒加载模式,当js中导入时才加载,但是上面的这两种情况需要在程序启动时就加载,文档意思说是防止死锁,但我没想到应用场景,自己项目中注释掉p和q,也是能正常工作的。这样发现的问题就是一些系统自己使用的模块没有初始化而无法工作,例如RCTDevLoadingView。
2020.7.23新增:0.61版本
1、初始化js线程,启动runloop
2、加载jsbundle源码
3、宏导出的模块在runtime阶段调用的+load方法中将所有模块加载进了一个数组。在这里遍历数组,初始化模块,生成配置表
4、创建js引擎_javaScriptExecutor
5、将配置表注入到js引擎,双端一致
模块内部导出的方法,那个宏定义是多生成了一个特殊前缀的+号方法,在调用模块方法时懒加载模块的所有导出方法(利用runtime查找模块的所有+号方法,抽出特殊前缀的那些方法的集合)。例如导出了一个test方法,实际上会生成-test和+__rct_export__test这样的两个方法,前者是为了在该方法中还可以调用类中的其他方法,后者是专门为了标示,方便runtime时查找的,+号方法而不是-号方法,猜测是一般类中+号方法较少,运行时查找更方便,也可以减少方法同名冲突。