⚠️ For Your Infomation:笔者非专业客户端开发人员,以下分析过程并不保证完全正确,并且加载过程是点到为止,不涉及 JS 解析部分,请酌情阅读

Bundle 入口函数

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 模式的 Bundle

Debug 对应的是以下结果,核心调用的是 RCTBundleURLProviderjsBundleURLForBundleRoot 函数。

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;
}

jsBundleURLForBundleRoot

这个函数首先会调用 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];
}

Release 模式的 Bundle

return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

NSBundle 可以大概理解为是一个类似 jar 的文件,里面封装了 app 代码、类库代码、资产文件等。NSBundle 通过对外暴露一个接口来描述内部结构与文件

mainBundle 表示获取包含当前执行中的代码的 bundle,大概类似于 Webpack 的 Initial Chunk 概念。通过 URLForResource 找到 bundle 里名为 "main" 后缀为 "jsbundle" 的文件,并返回其本地 URL,也就是 RN 的 JS Bundle 文件