⚠️ For Your Infomation:笔者非专业客户端开发人员,以下分析过程并不保证完全正确,并且加载过程是点到为止,不涉及 JS 解析部分,请酌情阅读
在 AppDelegate.m
文件,可找到函数 sourceURLForBridge
,这函数将返回一个 NSURL
对象,用于表示 Bundle 位置
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
Debug 对应的是以下结果,核心调用的是 RCTBundleURLProvider
的 jsBundleURLForBundleRoot
函数。
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]
sharedSettings
函数定义如下,就是返回一个 RCTBundleURLProvider
单例
+ (instancetype)sharedSettings
{
static RCTBundleURLProvider *sharedInstance;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
sharedInstance = [RCTBundleURLProvider new];
});
return sharedInstance;
}
这个函数首先会调用 packagerServerHostPort
获取打包器的地址,默认是 "localhost"
,我们这只考虑这种情况,然后将字符串 bundleRoot
和地址传入同名的类静态函数 jsBundleURLForBundleRoot
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:(NSURL * (^)(void))fallbackURLProvider
{
NSString *packagerServerHostPort = [self packagerServerHostPort];
// 这里猜测是 Metro
if (!packagerServerHostPort) {
...
} else {
return [RCTBundleURLProvider jsBundleURLForBundleRoot:bundleRoot
packagerHost:packagerServerHostPort
packagerScheme:[self packagerScheme]
enableDev:[self enableDev]
enableMinification:[self enableMinification]
modulesOnly:NO
runModule:YES];
}
这个类函数简单来说就是拼接好打包器的 URL,并将其封装为 NSURL
对象
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
packagerScheme:(NSString *)scheme
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
modulesOnly:(BOOL)modulesOnly
runModule:(BOOL)runModule
{
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
#ifdef HERMES_BYTECODE_VERSION
NSString *runtimeBytecodeVersion = [NSString stringWithFormat:@"&runtimeBytecodeVersion=%u", HERMES_BYTECODE_VERSION];
#else
NSString *runtimeBytecodeVersion = @"";
#endif
// When we support only iOS 8 and above, use queryItems for a better API.
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@&modulesOnly=%@&runModule=%@%@",
enableDev ? @"true" : @"false",
enableMinification ? @"true" : @"false",
modulesOnly ? @"true" : @"false",
runModule ? @"true" : @"false",
runtimeBytecodeVersion];
NSString *bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey];
if (bundleID) {
query = [NSString stringWithFormat:@"%@&app=%@", query, bundleID];
}
// 个人认为这部分有点过度封装,总之就是将以上的参数转为 NSURL
return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost scheme:scheme query:query];
}
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
NSBundle
可以大概理解为是一个类似 jar
的文件,里面封装了 app 代码、类库代码、资产文件等。NSBundle
通过对外暴露一个接口来描述内部结构与文件
mainBundle
表示获取包含当前执行中的代码的 bundle,大概类似于 Webpack 的 Initial Chunk 概念。通过 URLForResource
找到 bundle 里名为 "main"
后缀为 "jsbundle"
的文件,并返回其本地 URL,也就是 RN 的 JS Bundle 文件