rn應(yīng)用啟動時询微,利用RCTBundleURLProvider獲取到j(luò)s端入口文件index.ios.js的url地址,再生成程序根視圖RCTRootView撑毛。RCTRootView的創(chuàng)建方法
- (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书聚,在調(diào)用initWithBridge方法,在這個方法中注冊一系列通知藻雌,用來監(jiān)聽js文件的加載過程雌续,在文件加載完成后刷新視圖等。RCTRootView內(nèi)部有屬性強引用bridge對象胯杭。
bridge的創(chuàng)建方法主要調(diào)用的是如下
- (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);
---------- 標(biāo)記@1
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSData *sourceCode;
---------- 標(biāo)記@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
}];
---------- 標(biāo)記@3
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
RCTPerformanceLogger *performanceLogger = self->_performanceLogger;
__block NSString *config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
---------- 標(biāo)記@4
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
---------- 標(biāo)記@5
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
[weakSelf setUpExecutor];
[performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
});
---------- 標(biāo)記@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, @"");
}
});
---------- 標(biāo)記@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);
});
});
---------- 標(biāo)記@8
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode];
}
});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
@1: 創(chuàng)建外層的隊列組A
@2: 隊列組A中的第一個任務(wù),加載我們寫的那些js源碼文件
@3: 同步的形式糯景,將OC中通過宏RCT_EXPORT_MODULE導(dǎo)出的所有模塊信息嘁圈,例如模塊名字、模塊對應(yīng)的RCTModuleData實例等添加到屬性_moduleDataByID等之中蟀淮。同時初始化所有不能懶加載的模塊實例最住。
@4: 創(chuàng)建隊列組B,該隊列組用于A的一個子任務(wù)之中灭贷。意思就是隊列組A有一個任務(wù)温学,這個任務(wù)內(nèi)部又創(chuàng)建了一個隊列組B,B隊列組又包含了兩個任務(wù)甚疟,分別是@5和@6仗岖。
@5: 隊列組B的子任務(wù),初始化js執(zhí)行器_javaScriptExecutor览妖。
@6: 將@3已經(jīng)裝載的模塊信息轧拄,按照一定格式組裝結(jié)構(gòu)。
@7: @5讽膏、@6完成檩电,將模塊信息注入js執(zhí)行上下文中,這樣js代碼就可以調(diào)用了府树。
@8: @5俐末、@6完成后,未等@7完成奄侠,隊列組即已經(jīng)退出卓箫,執(zhí)行@8操作。執(zhí)行上面加載的js代碼垄潮,通知RootView頁面刷新等烹卒。
細(xì)究部分:
@3: initModulesWithDispatchGroup方法的實現(xiàn)
- (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)導(dǎo)出的模塊名字(若宏后面沒寫名字,則默認(rèn)為文件名字)弯洗,遍歷旅急,包裝成RCTModuleData類型的數(shù)據(jù),存到RCTBatchedBridge的三個屬性_moduleDataByID牡整、_moduleDataByName藐吮、_moduleClassesByID之中。
@3內(nèi)部方法prepareModulesWithDispatchGroup實現(xiàn)
- (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]; --------- 標(biāo)記p
[moduleData gatherConstants]; --------- 標(biāo)記q
[self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread];
}
};
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
}}
意思是找出所有模塊中需要預(yù)先實例化的模塊,初始化它炎码。具體是如果當(dāng)前模塊實現(xiàn)了init或者constantsToExport方法盟迟,則上面屬性requiresMainQueueSetup和hasConstantsToExport屬性會為YES秋泳,進(jìn)入標(biāo)記p和標(biāo)記q處潦闲,實例化模塊。
一般模塊采用的是懶加載模式迫皱,當(dāng)js中導(dǎo)入時才加載歉闰,但是上面的這兩種情況需要在程序啟動時就加載,文檔意思說是防止死鎖卓起,但我沒想到應(yīng)用場景和敬,自己項目中注釋掉p和q,也是能正常工作的戏阅。這樣發(fā)現(xiàn)的問題就是一些系統(tǒng)自己使用的模塊沒有初始化而無法工作昼弟,例如RCTDevLoadingView。
2020.7.23新增:0.61版本
1奕筐、初始化js線程舱痘,啟動runloop
2、加載jsbundle源碼
3离赫、宏導(dǎo)出的模塊在runtime階段調(diào)用的+load方法中將所有模塊加載進(jìn)了一個數(shù)組芭逝。在這里遍歷數(shù)組,初始化模塊渊胸,生成配置表
4旬盯、創(chuàng)建js引擎_javaScriptExecutor
5、將配置表注入到j(luò)s引擎翎猛,雙端一致
模塊內(nèi)部導(dǎo)出的方法胖翰,那個宏定義是多生成了一個特殊前綴的+號方法,在調(diào)用模塊方法時懶加載模塊的所有導(dǎo)出方法(利用runtime查找模塊的所有+號方法切厘,抽出特殊前綴的那些方法的集合)萨咳。例如導(dǎo)出了一個test方法,實際上會生成-test和+__rct_export__test這樣的兩個方法迂卢,前者是為了在該方法中還可以調(diào)用類中的其他方法某弦,后者是專門為了標(biāo)示,方便runtime時查找的而克,+號方法而不是-號方法靶壮,猜測是一般類中+號方法較少,運行時查找更方便员萍,也可以減少方法同名沖突腾降。