穿越虚拟门槛:揭秘iOS中React Native页面的加载与渲染

一、引言

近年来,移动应用开发领域发展迅猛,不同的技术框架和平台涌现出来,为开发者提供了更多的选择和灵活性。React Native作为一种跨平台移动应用开发框架,受到了广泛的关注和使用。它结合了React的声明式编程模型和原生移动应用的性能与体验,使开发人员能够用JavaScript构建高效、可靠的原生应用。然而,React Native并非完全摆脱了原生开发的依赖。在iOS平台上,React Native需要通过一定的集成和加载过程,使其能够在iOS设备上运行。本文将基于0.63.0版本的React Native,深入探讨iOS加载React Native的过程,帮助开发者理解React Native在iOS上的工作原理。

首先,初始化React Native项目后,iOS/AppDelegate.m 文件会自动生成相关的代码片段,这些代码片段将用于在iOS应用中加载和初始化React Native框架,具体的代码内容如下:

ini 复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	... 
  
	// 1.初始化bridge
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  // 2.创建rootView
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"rnNativeSourceCodeLearnDemo"
                                            initialProperties:nil];

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  // 3.创建跟控制器
  UIViewController *rootViewController = [UIViewController new];
  // 4.将rootViewController上面的视图替换为rootView
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  // 5.window显示
  [self.window makeKeyAndVisible];
  return YES;
}

我们发现在这部分代码中一共做了三件事情:

  • 初始化一个 RCTBridge
  • 利用 RCTBridge 初始化一个 RCTRootView
  • RCTRootView 赋值给 controllerview

我们先来看看 RCTRootView 到底做了啥?和我们本次想要讲解的主题有何关系?

先阅RCTRootView

看到现在可能有同学在想,为啥还没直入主题,讲解 iOS是如何加载RN的呢?

之所以先简单讲述一下 RCTRootView 是为了让各位同学更好的理解 RCTRootViewRCTBridge 的关系;因为很多同学在网上查阅 RN 页面初始化发现有很多种方式,其实您会发现都是一样的。为何?请看下面:

objectivec 复制代码
// RCTRootView.h
@interface RCTRootView : UIView

- (instancetype)initWithBridge:(RCTBridge *)bridge
                    moduleName:(NSString *)moduleName
             initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(nullable NSDictionary *)initialProperties
                    launchOptions:(nullable NSDictionary *)launchOptions;
// ...
@end

RCTRootView.h 声明文件中我们发现如下两个讯息:

  • RCTRootView 继承自 UIView,表明其是一个 UI 展示的组件;
  • RCTRootView 加载资源初始化有两种方式 initWithBridgeinitWithBundleURL 两种方式

再来看看 RCTRootView.minitWithBridgeinitWithBundleURL 两种方法的实现:

objectivec 复制代码
//RCTRootView.m
@implementation RCTRootView {
  RCTBridge *_bridge;
  NSString *_moduleName;
  RCTRootContentView *_contentView;
  BOOL _passThroughTouches;
  CGSize _intrinsicContentSize;
}

- (instancetype)initWithBridge:(RCTBridge *)bridge
                    moduleName:(NSString *)moduleName
             initialProperties:(NSDictionary *)initialProperties
{
  // ...

  if (self = [super initWithFrame:CGRectZero]) {
    // ...
    // 注册名为RCTJavaScriptWillStartLoadingNotification的消息通知,当jsbunlde将要加载时会触发bridgeDidReload
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(bridgeDidReload)
                                                 name:RCTJavaScriptWillStartLoadingNotification
                                               object:_bridge];
    // 注册名为RCTJavaScriptDidLoadNotification的消息通知,当jsbundle执行完成之后会会调用javaScriptDidLoad
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(javaScriptDidLoad:)
                                                 name:RCTJavaScriptDidLoadNotification
                                               object:_bridge];
    // 注册名为RCTContentDidAppearNotification的消息通知,当内容以及展示则会关闭加载进度条
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(hideLoadingView)
                                                 name:RCTContentDidAppearNotification
                                               object:self];
    // iOS版本的rn还支持展示loadingview哦
    [self showLoadingView];

    // Immediately schedule the application to be started.
    // (Sometimes actual `_bridge` is already batched bridge here.)
    [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
  }
  return self;
}
// initWithBundleURL的实现
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions
{
  // 也是和initWithBridge一样,只是不需要自己手动实例化 RCTBridge
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions];

  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}

- (void)javaScriptDidLoad:(NSNotification *)notification
{
  // 获取到RCTBridge的实例batchedBridge(可能有点超前了,后面会将)
  RCTBridge *bridge = notification.userInfo[@"bridge"];
  if (bridge != _contentView.bridge) {
    [self bundleFinishedLoading:bridge];
  }
}

- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
  // ...
  [_contentView removeFromSuperview];
  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
                                                    bridge:bridge
                                                  reactTag:self.reactTag
                                           sizeFlexiblity:_sizeFlexibility];
  // 利用RCTBridge调用js方法,启动页面
  [self runApplication:bridge];
  // 展示页面
  [self insertSubview:_contentView atIndex:0];
}

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag" : _contentView.reactTag,
    @"initialProps" : _appProperties ?: @{},
  };
  // 执行JavaScript中的方法AppRegistry.runApplication
  [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
}
@end

查阅上面的源码我们发现:

  • initWithBundleURLinitWithBridge 两种实现方式,其本质都是使用的 initWithBridge 将 RCTBridge 传入;
  • RCTBridgeRCTRootView 注册了各种监听,当资源在加载完成之后执行 react 代码并展示页面

好吧,我承认,如果您仔细看到这里,是不是有很多疑问;什么 RCTRootView, RCTBridge , batchedBridge , enqueueJSCall,what , 这些都是个啥?下面我将结合RN加载的简易流程图一一告诉您(tips: 为什么先是简易流程?因为作者不想一下子灌输太多的概念让大家蒙圈,详细流程请见之后的 RN 加载时序图)。

二、RCTBridge初始化

RCTBridge 是React Native框架中的一个重要组件之一。它作为JavaScript和原生代码之间的通信桥梁,允许React Native应用程序和原生模块之间相互调用和交换数据。

在React Native应用程序中,JavaScript代码运行在一个独立的JavaScript线程中,而原生代码运行在主线程中。 RCTBridge 负责管理这两个线程之间的通信。它提供了一种机制,使得JavaScript代码可以调用原生模块的方法和函数,并传递参数。同样地,原生模块也可以通过RCTBridge将数据传递给JavaScript代码。

因此,可以说RCTBridge的初始化实际上是整个React Native加载的初始化过程。下面我们来逐个查阅 RCTBridge 初始化过程中的几个重要方法:

ini 复制代码
// RCTBridge.m
// ...
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
  // RCTBridge initWithDelegate的初始化方法都会进入到initWithDelegate_bundleURL_moduleProvider_launchOptions方法中
  return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions];
}

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                   moduleProvider:(RCTBridgeModuleListProvider)block
                    launchOptions:(NSDictionary *)launchOptions
{
  // RCTBridge initWithDelegate的初始化方法都会进入到initWithDelegate_bundleURL_moduleProvider_launchOptions方法中
  return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions];
}

- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
                       bundleURL:(NSURL *)bundleURL
                  moduleProvider:(RCTBridgeModuleListProvider)block
                   launchOptions:(NSDictionary *)launchOptions
{
  if (self = [super init]) {
    _delegate = delegate;
    _bundleURL = bundleURL;
    _moduleProvider = block;
    _launchOptions = [launchOptions copy];
    // 看这里
    [self setUp];
  }
  return self;
}

- (void)setUp
{
  // 获取bridgeClass 默认是RCTCxxBridge
  Class bridgeClass = self.bridgeClass;
  // 只有bundleURL的值发生变化才会更新bundleURL
  NSURL *previousDelegateURL = _delegateBundleURL;
  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
    _bundleURL = _delegateBundleURL;
  }
  // 初始化RTCxxBridge
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  // 启动RTCxxBridge
  [self.batchedBridge start];
}
// 使用RCTCxxBridge也就是self.batchedBridge
- (Class)bridgeClass
{
  return [RCTCxxBridge class];
}
// ...

RCTBridge 初始化方法一共做了如下几件事情:

  1. 调用 init 初始化 RCTBridge 实例;
  2. 调用 RTCxxBridgeinitWithParentBridge 方法初始化 RTCxxBridge 实例
  3. 调用 self.batchedBridgestart 方法启动

RCTCxxBridge start

上面部分 RCTBridge 初始化了 RCTCxxBridge 的实例,并调用了 start 方法,该方法的执行步骤如图所示:

下面我们来具体看看:

ini 复制代码
// RCTCxxBridge.m

- (void)start
{
  //1. 发送RCTJavaScriptWillStartLoadingNotification消息通知以供RCTRootView接收并处理
  [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification
                                                      object:_parentBridge
                                                    userInfo:@{@"bridge" : self}];

  //2. 提前设置并开启JS线程 _jsThread
  _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
  _jsThread.name = RCTJSThreadName;
  _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
  _jsThread.stackSize *= 2;
#endif
  // 启动JS线程
  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();
  //3. 注册native modules
  [self registerExtraModules];
  // 重点:注册所有的自定义Native Module;包括您在rn官网上看到的原生模块定义以及RN自带的Text,Date等原生组件
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
    // 初始化所有懒加载的native module

  [self registerExtraLazyModules];

  // 其实这里不会做任何事情,详情请见initializeBridge
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;

  //4. 准备executor factory; 看RCTBridge是否指定了executorClass
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {// 如果没有指定executorClass 但是实现了RCTCxxBridgeDelegate协议,那么就使用jsExecutorFactoryForBridge的方式 准备 executor factory 否则就使用make_shared初始化一个空的JSCExecutorFactory
    if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
      id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>)self.delegate;
      executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
    }
    if (!executorFactory) {
      executorFactory = std::make_shared<JSCExecutorFactory>(nullptr);
    }
  } else {// 如果指定了 executorClass  就使用指定的executorClass 初始化;一般RCTObjcExecutorFactory为开发环境使用的
    id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
    executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
      if (error) {
        [weakSelf handleError:error];
      }
    }));
  }

  // 5. module初始化完成就初始化底层Instance实例,也就是_reactInstance
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    // 利用executorFactory来initializeBridge 方法;完成初始化_reactInstance(也就是Instance)
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

  //6. 异步加载js代码
  dispatch_group_enter(prepareBridge);
  __block NSData *sourceCode;
  [self
      loadSource:^(NSError *error, RCTSource *source) {
        if (error) {
          [weakSelf handleError:error];
        }

        sourceCode = source.data;
        dispatch_group_leave(prepareBridge);
      }
      onProgress:^(RCTLoadingProgress *progressData) {
#if (RCT_DEV | RCT_ENABLE_LOADING_VIEW) && __has_include(<React/RCTDevLoadingViewProtocol.h>)
        id<RCTDevLoadingViewProtocol> loadingView = [weakSelf moduleForName:@"DevLoadingView"
                                                      lazilyLoadIfNecessary:YES];
        [loadingView updateProgress:progressData];
#endif
      }];

  // 7. 等待native moudle 和 JS 代码加载完毕后就执行JS; dispatch_group_t和dispatch_group_notify联合使用保证异步代码同步按顺序执行
  dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      // 重点,执行JS代码;后面我们会具体展开分析
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
}

- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  __weak RCTCxxBridge *weakSelf = self;
  // 创建消息队列
  _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
    if (error) {
      [weakSelf handleError:error];
    }
  });

  // This can only be false if the bridge was invalidated before startup completed
  if (_reactInstance) {
    [self _initializeBridgeLocked:executorFactory];
  }

}

- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  std::lock_guard<std::mutex> guard(_moduleRegistryLock);
  // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
  _reactInstance->initializeBridge(
      std::make_unique<RCTInstanceCallback>(self),
      executorFactory,
      _jsMessageThread,
      [self _buildModuleRegistryUnlocked]);
  _moduleRegistryCreated = YES;
}

讲真,RCTCxxBridgestart 方法中,代码比较多;现在我们来梳理一下以上的 7 个步骤:

  1. 发送 RCTJavaScriptWillStartLoadingNotification 消息通知以供 RCTRootView 接收并处理;

  2. 开启 js 线程 _jsThread, 并绑定了 runRunLoop,后续所有的 js 代码都在这个线程里面执行;

  3. 注册所有 native modules:

    • 所有的自定义 JS 和 iOS 通信的 native module,包括 RN 自带的 Text, Date 等;
    • 初始化阶段编写的 module,native module 会被转换成RCTModuleData分别存入字典和数组中;
    • 所有懒加载的 native modules,主要是在调试时,动态的注入 native modules。
  4. 准备 js 和 Native 之间的桥和 js 运行环境,初始化 JSExecutorFactory 实例;

  5. 在 JS 线程中创建 RCTMessageThread,初始化 _reactInstance(Instance 实例);

  6. 异步加载 js 代码

  7. 等待 native modules 和 js 代码都加载完成则执行 js 代码;

我们发现,react-native 中很多初始化的工作都是异步执行的,使用了 dispatch_group_t,然而,最后都会使用 dispatch_group_notify,也就是等待所有的异步初始化工作执行完成之后,才会执行最后的 js 代码执行操作。

JSExecutorFactory创建了什么

上面谈到了通过创建一个 JSExecutorFactory 来创建 Instance,下面我们就来看看 JSExecutorFactory 到底是干嘛的。

JSExecutorFactory,顾名思义用于生产 JSExecutor 实例,JSExecutor 用于执行 JS,也是 JS 和 Native 之间的桥梁。无论是 Native call JS 还是 JS call Native,JSExecutor 都起到了至关重要的作用。

当在 React Native 框架中使用 JSExecutorFactory 时,它的作用是创建 JavaScript 执行器。

JavaScript 执行器是 React Native 的核心组件之一,用于在移动设备上执行JS代码。它的主要责任是解析和执行JS代码,并将执行结果返回给应用程序。React Native 提供了多种执行器选项,包括 Hermes、JSC(JavaScriptCore)和 V8。

此外,通过 JSExecutorFactory,开发人员可以根据性能和兼容性需求选择不同的执行器。例如,Hermes 执行器在性能方面表现出色,因此从 React Native 0.60 版本开始成为默认执行器。而 JSC 和 V8 执行器在某些情况下可能具有更好的兼容性。

总结一下,JSExecutorFactory 是一个用于创建 JavaScript 执行器的接口,在 React Native 应用程序中起到关键作用。它允许开发人员根据应用需求选择合适的执行器,并通过 JSExecutorFactory 进行配置和使用。

c 复制代码
// JSExecutorFactory.m

// 有一个createJSExecutor方法
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> __unused jsQueue)
{
  // ...
  // 默认的情况创建的是JSIExecutor;但是具体的createJSExecutor方法在什么时候调用,还得看下面的分析;只是在这里,我们清楚了 createJSExecutor 创建的是 JSIExecutor
  return std::make_unique<JSIExecutor>(
      facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, std::move(installBindings));
}

Instance初始化

在使用JavaScript执行器时,我们通常需要维护一个全局的执行器实例,以便在整个应用程序中共享和重复使用。这样可以避免反复创建和销毁执行器的开销,并确保在不同的代码段中能够共享相同的执行环境和上下文。同时通过将instance初始化为一个单例(Singleton)对象,我们可以保证在整个应用程序中只有一个执行器实例存在。这样做的好处是可以减少资源的消耗,提高执行效率,并确保代码的一致性和可维护性。

通过上面的 RCTxCxBridge 的 start 方法调用链,我们发现在 _initializeBridgeLocked 中初始化了_reactInstance;

rust 复制代码
// RCTCxxBridge.m
- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  std::lock_guard<std::mutex> guard(_moduleRegistryLock);
  // 初始化_reactInstance
  _reactInstance->initializeBridge(
      std::make_unique<RCTInstanceCallback>(self),
      executorFactory,
      _jsMessageThread,
      [self _buildModuleRegistryUnlocked]);
  _moduleRegistryCreated = YES;
}


// Instance.cpp
void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback,
    std::shared_ptr<JSExecutorFactory> jsef,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<ModuleRegistry> moduleRegistry) {
   jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
    // 使用JSExecutorFactory工厂创建一个JSExecutor来初始化NativeToJsBridge
    nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
        jsef.get(), moduleRegistry_, jsQueue, callback_);
    // 执行initializeRuntime方法
    nativeToJsBridge_->initializeRuntime();

     // 当NativeToJsBridge被创建之后,jsi::Runtime就会存在,当然 前创建 js消息队列也会存在,此时可以将当前创建的NativeToJsBridge放入消息队列中(此处我们暂不具体深究消息队列是干嘛的,后面会具体讲述)
    jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);

    std::lock_guard<std::mutex> lock(m_syncMutex);
    m_syncReady = true;
    m_syncCV.notify_all();
  });

}

NativeToJsBridge的初始化

在实例化 JSExecutorFactory 并初始化 instance 之后,我们还需要考虑初始化 NativeToJsBridgeNativeToJsBridge 是一个重要的组件,用于实现JavaScript与本地代码之间的通信。它提供了一种机制,使得JavaScript代码可以调用本地代码的功能,并且本地代码可以将结果返回给JavaScript环境。

通过初始化 NativeToJsBridge,我们能够确保在应用程序的各个部分都可以方便地使用桥接器进行JavaScript和本地代码之间的交互。这样,无论是在UI层还是在后台逻辑中,我们都能够轻松地调用本地方法或处理JavaScript的调用请求。

NativeToJsBridge 的初始化是在 InstanceinitializeBridge 方法中触发的,下面我们就来看看具体 NativeToJsBridge 具体是什么做的:

c 复制代码
// NativeToJsBridge.cpp
// NativeToJsBridge的初始化
NativeToJsBridge::NativeToJsBridge(
    JSExecutorFactory *jsExecutorFactory,
    std::shared_ptr<ModuleRegistry> registry,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<InstanceCallback> callback)
    : m_destroyed(std::make_shared<bool>(false)),
      m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
      m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
      m_executorMessageQueueThread(std::move(jsQueue)),
      m_inspectable(m_executor->isInspectable()) {}

在初始化方法中主要做了如下几件事情:

  • 其中 registrycallback 作为入参生成了一个 JsToNativeBridge 类型实例赋值给 m_delegate
  • 使用 JSExecutorFactory_delegate 创建了一个 JSExecutor,从 RCTCxxBridge 中创建的 JSExecutorFactory 得知此 JSExecutor 就是 JSIExecutor
  • 设置消息队列

此外,我们从 NativeToJsBridge 得知一个名叫 ModuleRegistry 的参数;那么 ModuleRegistry 具体有什么作用请见下面的代码分析:

c 复制代码
/RCTCxxBridge.m
- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked
{
  if (!self.valid) {
    return {};
  }

  __weak __typeof(self) weakSelf = self;
  ModuleRegistry::ModuleNotFoundCallback moduleNotFoundCallback = ^bool(const std::string &name) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    return [strongSelf.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] &&
        [strongSelf.delegate bridge:strongSelf didNotFindModule:@(name.c_str())];
  };
  // 创建ModuleRegistry;通过_moduleDataByID<RCTModuleData>并创建所有自定义的NativeModule;也就是说 所有原生组件的加载其实把并不是在此处,而是存放在了一个名叫_moduleDataByID数组中
  auto registry = std::make_shared<ModuleRegistry>(
      createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback);

  return registry;
}

// RCTCxxUtils.m

std::vector<std::unique_ptr<NativeModule>>
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
{
  std::vector<std::unique_ptr<NativeModule>> nativeModules;
  for (RCTModuleData *moduleData in modules) {
    // 您可以尝试着打印,您自定义的原生模块是否被加载
      NSLog(@"原生模块: %@",moduleData.name);
    if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
      nativeModules.emplace_back(std::make_unique<CxxNativeModule>(
          instance,
          [moduleData.name UTF8String],
          [moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },
          std::make_shared<DispatchMessageQueueThread>(moduleData)));
    } else {
      nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge, moduleData));
    }
  }
  return nativeModules;
}

JSIExecutor

前面我们已经将 RCTBridge 的初始化基本讲解完成,还剩余最后一个部分,就是 JSIExecutor; 从前面的部分我们知道 NativeToJsBridge 持有 JSIExecutor 的实例(因为 JSIExecutor 是通过 JSExecutorFactory 创建的,JSExecutorFactory 是在 RCTCxxBridge start 的时候初始化,但是正式调用 createJSExecutor 却是在 NativeToJsBridge 中);

InstanceNativeToJsBridge 一样,JSIExecutor 主要用来 Native call JS,但他是比 InstanceNativeToJsBridge 更深层次的一个核心类;他会直接和 iOS 的 JavaScriptCore 进行通信。

JSIExector属性

JSIExecutor 一共有几个关键的属性:

c 复制代码
  std::shared_ptr<jsi::Runtime> runtime_;
  std::shared_ptr<ExecutorDelegate> delegate_;
  std::shared_ptr<JSINativeModules> nativeModules_;
  std::once_flag bindFlag_;
  std::unique_ptr<RAMBundleRegistry> bundleRegistry_;
  • <jsi::Runtime> runtime_:Runtime 类型指针,代表 JS 的运行时。这是一个抽象类,其实际上是由 JSCRuntime 来实现的。JSCRuntime 实现了<jsi::Runtime>接口,提供了创建 JS 上下文的功能,同时可以执行 JS。如下是 JSCRuntimeevaluateJavaScript 方法实现:
arduino 复制代码
jsi::Value JSCRuntime::evaluateJavaScript(
    const std::shared_ptr<const jsi::Buffer> &buffer,
    const std::string &sourceURL) {
  std::string tmp(
      reinterpret_cast<const char *>(buffer->data()), buffer->size());
  JSStringRef sourceRef = JSStringCreateWithUTF8CString(tmp.c_str());
  JSStringRef sourceURLRef = nullptr;
  if (!sourceURL.empty()) {
    sourceURLRef = JSStringCreateWithUTF8CString(sourceURL.c_str());
  }
  JSValueRef exc = nullptr;
  // JSEvaluateScript是JavaScriptCore中用于执行js代码的工具
  JSValueRef res =
      JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
  JSStringRelease(sourceRef);
  if (sourceURLRef) {
    JSStringRelease(sourceURLRef);
  }
  checkException(res, exc);
  return createValue(res);
}
  • <ExecutorDelegate> delegate_ExecutorDelegate 类型的指针,这里的 ExecutorDelegate 是抽象类,实际是由 JsToNativeBridge 实现的。

    c 复制代码
    // NativeToJsBridge.cpp
    NativeToJsBridge::NativeToJsBridge(
        JSExecutorFactory *jsExecutorFactory,
        std::shared_ptr<ModuleRegistry> registry,
        std::shared_ptr<MessageQueueThread> jsQueue,
        std::shared_ptr<InstanceCallback> callback)
        : m_destroyed(std::make_shared<bool>(false)),
          m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
          m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
          m_executorMessageQueueThread(std::move(jsQueue)),
          m_inspectable(m_executor->isInspectable()) {}
  • <JSINativeModules> nativeModules_JSINativeModules 由上层传入的 ModuleRegistry 构造而成,而 ModuleRegistry 又是在 RCTxxBridge 中构造而成。

JSINativeModules 一共有两个方法:getModulecreateModule

scss 复制代码
// JSINativeModules.cpp
Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name) {
  if (!m_moduleRegistry) {
    return nullptr;
  }

  std::string moduleName = name.utf8(rt);

  const auto it = m_objects.find(moduleName);
  if (it != m_objects.end()) {
    return Value(rt, it->second);
  }
  // 调用createModule方法
  auto module = createModule(rt, moduleName);
  if (!module.hasValue()) {
    // Allow lookup to continue in the objects own properties, which allows for
    // overrides of NativeModules
    return nullptr;
  }

  auto result =
      m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  return Value(rt, result->second);
}


folly::Optional<Object> JSINativeModules::createModule(
    Runtime &rt,
    const std::string &name) {
  bool hasLogger(ReactMarker::logTaggedMarker);
  if (hasLogger) {
    ReactMarker::logTaggedMarker(
        ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
  }
  // runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
  // JS端的函数__fbGenNativeModule调用最终就会走到这里
  if (!m_genNativeModuleJS) {
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }

  auto result = m_moduleRegistry->getConfig(name);
  if (!result.hasValue()) {
    return folly::none;
  }
   // 调用m_genNativeModuleJS函数,即__fbGenNativeModule
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,
      valueFromDynamic(rt, result->config),
      static_cast<double>(result->index));
  CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";

  folly::Optional<Object> module(
      moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));

  if (hasLogger) {
    ReactMarker::logTaggedMarker(
        ReactMarker::NATIVE_MODULE_SETUP_STOP, name.c_str());
  }

  return module;
}

看到 JSINativeModules 的两个方法,好像打开了一扇大门,但是又有一些疑惑?

  • createModulegetModule 中调用,那么 getModule 再哪里被使用?
  • _fbGenNativeModule 是什么?难道 js 和 RN 是通过_fbGenNativeModule进行通信的吗?

首先是是问题1,在 getModule 方法上按住 CMD 然后点击 caller,我们发现全局唯一使用的地方在 JSIExecutor 中的 NativeModuleProxy 中:

arduino 复制代码
// JSIExecutor.cpp

class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  // 构造函数 JSIExecutor实例作为NativeModuleProxy构造函数的入参
  NativeModuleProxy(std::shared_ptr<JSINativeModules> nativeModules)
      : weakNativeModules_(nativeModules) {}
 // NativeModuleProxy 的 get方法 用于获取native module信息
  Value get(Runtime &rt, const PropNameID &name) override {
    return nativeModules->getModule(rt, name);
  }

  void set(Runtime &, const PropNameID &, const Value &) override {
    throw std::runtime_error(
        "Unable to put on NativeModules: Operation unsupported");
  }

 private:
  std::weak_ptr<JSINativeModules> weakNativeModules_;
};

那么 NativeModuleProxy 这个类又是在哪里使用的呢?全局搜索 NativeModuleProxy,您会发现只有一个地方在使用 NativeModuleProxy,就是 JSIExecutorinitializeRuntime 方法,源码如下:

php 复制代码
void JSIExecutor::initializeRuntime() {
  SystraceSection s("JSIExecutor::initializeRuntime");
  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
// ...
}

JSIExecutorinitializeRuntime 的调用链为:

RCTxxBridge.start->Instance.initializeBridge-> NativeToJSBridge.initializeRuntime->JSIExecutor.initializeRuntime,说明在 runtime 时设置 nativeModuleProxy 是在初始化的时候执行的。

runtime 是一个 JSCRuntime 类型对象,通过调用 rumtime_->global() 获得一个全局的 global 对象。然后又通过 setProperty 方法给 global 对象设置了一个名为 nativeModuleProxy 的对象。日后(JS 侧的)global 对象通过"nativeModuleProxy"这个名字即可访问到(native 侧的)NativeModuleProxy

typescript 复制代码
// react-native/Libraries/BatchedBridge/NativeModules.js
let NativeModules: { [moduleName: string]: Object, ... } = {};
if (global.nativeModuleProxy) {
  // 使用nativeModuleProxy
  NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
  const bridgeConfig = global.__fbBatchedBridgeConfig;

  const defineLazyObjectProperty = require("../Utilities/defineLazyObjectProperty");
  (bridgeConfig.remoteModuleConfig || []).forEach(
    (config: ModuleConfig, moduleID: number) => {
      // Initially this config will only contain the module name when running in JSC. The actual
      // configuration of the module will be lazily loaded.
      const info = genModule(config, moduleID);
      if (!info) {
        return;
      }

      if (info.module) {
        NativeModules[info.name] = info.module;
      }
      // If there's no module config, define a lazy getter
      else {
        defineLazyObjectProperty(NativeModules, info.name, {
          get: () => loadModule(info.name, moduleID),
        });
      }
    }
  );
}

通过如上的代码,我们可以得到一个等式:JS 侧的 NativeModules == JS 侧的 global.nativeModuleProxy == native 侧 NativeModuleProxy = native 侧所有原生及自定义的 Native Module。

接下来,我们回答问题二:_fbGenNativeModule 是什么?难道 js 和 RN 是通过 _fbGenNativeModule 进行通信的吗?

首先,在 native 中通过 runtime 的方式获取到 js 的全局对象 _fbGenNativeModule;

scss 复制代码
// JSINativeModules.cpp

folly::Optional<Object> JSINativeModules::createModule(
    Runtime &rt,
    const std::string &name) {
  bool hasLogger(ReactMarker::logTaggedMarker);
  if (hasLogger) {
    ReactMarker::logTaggedMarker(
        ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
  }
  // runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
  // JS端的函数__fbGenNativeModule调用最终就会走到这里
  if (!m_genNativeModuleJS) {
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }

   // 调用m_genNativeModuleJS函数,即__fbGenNativeModule
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,
      valueFromDynamic(rt, result->config),
      static_cast<double>(result->index));
  CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";

  folly::Optional<Object> module(
      moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));

  return module;
}

接着,我们来看看 js 端是否有 _fbGenNativeModule 这个全局函数:

typescript 复制代码
// react-native/Libraries/BatchedBridge/NativeModules.js

function genModule(
  config: ?ModuleConfig,
  moduleID: number
): ?{
  name: string,
  module?: Object,
  ...
} {
  if (!config) {
    return null;
  }

  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;

  if (!constants && !methods) {
    // Module contents will be filled in lazily later
    return { name: moduleName };
  }

  const module = {};
  methods &&
    methods.forEach((methodName, methodID) => {
      const isPromise =
        promiseMethods && arrayContains(promiseMethods, methodID);
      const isSync = syncMethods && arrayContains(syncMethods, methodID);

      const methodType = isPromise ? "promise" : isSync ? "sync" : "async";
      module[methodName] = genMethod(moduleID, methodID, methodType);
    });

  Object.assign(module, constants);

  if (module.getConstants == null) {
    module.getConstants = () => constants || Object.freeze({});
  } else {
  }

  return { name: moduleName, module };
}

// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;

还真别说在 JS 端真存在一个 _fbGenNativeModule 的全局方法;其主要作用就是方便 native 调用 js 代码对应 module 下的方法。

_fbGenNativeModule 的主要作用是方便 native 侧调用 js 的模块下的方法;也就是native to JS。

至此,在 RCTBridge 初始化的过程讲解完毕,最后我们使用一个时序图来总结之前提到的各个主要类的执行顺序:

三、JS加载

通过上面的部分我们了解到 RN 代码加载使用的是 RCTJavaScriptLoaderloadBundleAtURL_onProgress_onComplete 的方法;我们可以使用 onProgress 监听加载进度,在 onComplete 中根据 error 是否为空判断加载是否成功,并且可以使用 source.data 获取到加载的二进制 JS 代码。

那么 RCTJavaScriptLoader 又是如何加载的?

go 复制代码
// RCTJavaScriptLoader.m
+ (void)loadBundleAtURL:(NSURL *)scriptURL
             onProgress:(RCTSourceLoadProgressBlock)onProgress
             onComplete:(RCTSourceLoadBlock)onComplete
{
  int64_t sourceLength;
  NSError *error;
  // 尝试使用同步加载的方式加载jsbundle
  NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL
                                          runtimeBCVersion:JSNoBytecodeFileFormatVersion
                                              sourceLength:&sourceLength
                                                     error:&error];
  if (data) {
    onComplete(nil, RCTSourceCreate(scriptURL, data, sourceLength));
    return;
  }

  const BOOL isCannotLoadSyncError = [error.domain isEqualToString:RCTJavaScriptLoaderErrorDomain] &&
      error.code == RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously;
    // 否则尝试使用异步加载jsbundle
  if (isCannotLoadSyncError) {
    attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
  } else {
    onComplete(error, nil);
  }
}

从上面的代码我们知道,jsbundle 的加载分为两种情况:

  • 同步加载
  • 异步加载

默认的情况,会尝试使用同步加载的方式,如果同步加载失败则使用异步加载的方式。

同步加载JS

由于同步加载代码较长笔者暂且保留重要部分,有兴趣同学可自行查阅。

arduino 复制代码
+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
                               runtimeBCVersion:(int32_t)runtimeBCVersion
                                   sourceLength:(int64_t *)sourceLength
                                          error:(NSError **)error
{
  // ...  此处部分进行了scriptURL非空判断
  // 如果bundle不再本地,那么就报错,不能同步加载Bundle
  if (!scriptURL.fileURL) {
    if (error) {
      *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
                                   code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
                               userInfo:@{
                                 NSLocalizedDescriptionKey :
                                     [NSString stringWithFormat:@"Cannot load %@ URLs synchronously", scriptURL.scheme]
                               }];
    }
    return nil;
  }

  // 通过bundle的前4个字节,可以判断出当前的bundle是普通的Bundle还是RAM bundle(RAM bundle前四个字节的值为0xFB0BD1E5)
  // RAM bundle 相比普通的bundle好处在于可以使用【懒加载】的方式将 module注入到JSC中
  // 使用fopen读取文件
  FILE *bundle = fopen(scriptURL.path.UTF8String, "r");
  if (!bundle) {
    if (error) {
      *error = [NSError
          errorWithDomain:RCTJavaScriptLoaderErrorDomain
                     code:RCTJavaScriptLoaderErrorFailedOpeningFile
                 userInfo:@{
                   NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error opening bundle %@", scriptURL.path]
                 }];
    }
    return nil;
  }
  // 读取header
  facebook::react::BundleHeader header;
  size_t readResult = fread(&header, sizeof(header), 1, bundle);
  // 文件读取之后记得关闭哦
  fclose(bundle);
  // ....
  // 通过header就可以知道是什么类型的Bundle了(请见下面的pareseTyoeFromHeader)
  facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header);
  switch (tag) {
    case facebook::react::ScriptTag::RAMBundle:
      break;

    case facebook::react::ScriptTag::String: {
      NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:error];
      if (sourceLength && source != nil) {
        *sourceLength = source.length;
      }
      return source;

    }
    case facebook::react::ScriptTag::BCBundle:
     // ...
        return nil;
      }
      break;
  }

  struct stat statInfo;
  if (sourceLength) {
    *sourceLength = statInfo.st_size;
  }
  // 返回jsbundle的二进制数据
  return [NSData dataWithBytes:&header length:sizeof(header)];
}

// JSBundleType.cpp

static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5;
static uint32_t constexpr BCBundleMagicNumber = 0x6D657300;

ScriptTag parseTypeFromHeader(const BundleHeader &header) {
  switch (folly::Endian::little(header.magic)) {
    case RAMBundleMagicNumber:
      return ScriptTag::RAMBundle;
    case BCBundleMagicNumber:
      return ScriptTag::BCBundle;
    default:
      return ScriptTag::String;
  }
}

通过上面的代码我们知道同步加载 jsbundle 一共做了 4 件事情:

  1. 判断 bundle 是否在本地,因为同步加载只加载本地 bundle;否则直接报错;
  2. 使用 fopen 读取本地 bundle;
  3. 通过 bundle 的前 4 个字节来判断 bundle 属于什么类型:RAMBundle , String , BCBundle;
  4. 返回 bundle 的二进制数据.

读到这里,是不是对 RAMBundle , String , BCBundle 这三种类型有疑问,他们分别是干嘛的;笔者就在此处给大家解答疑惑。

在 react-native 执行 JS 代码之前,必须将代码加载到内存中并进行解析。如果您加载了一个 50MB 的普通 Bundle,那么所有的 50MB 都必须被加载和解析才能被执行。RAM 格式的 Bundle 则对此进行了优化,即启动时只加载 50MB 中实际需要的部分,之后再逐渐按需加载更多的包。

  • RAMBundle : RAM bundle 相比普通的 bundle 好处在于可以使用懒加载的方式将 module 注入到 JSC 中; RAMBundle 是用 ram-bundle 命令打出来的bundle,它除了生成整合的 js 文件外,还会生成各个单独的未整合 js 文件,全部放在 js-modules 目录下, bundle 头四个字节固定为 0xFB0BD1E5. RAMBundle 的使用及设置,详情请见官网(opens new window)
  • BCBundle : BCBundle 是 js 字节码 bundle 类型; 不允许使用;
  • String : 普通的 bundle

异步加载JS

上面介绍了同步加载 bundle 就是读取本地磁盘预置或预先下载的 bundle 数据,所以不难判断异步加载 bundle 就是下载网络上的 bundle。下面我们来看下源码:

scss 复制代码
static void attemptAsynchronousLoadOfBundleAtURL(
    NSURL *scriptURL,
    RCTSourceLoadProgressBlock onProgress,
    RCTSourceLoadBlock onComplete)
{
 // 如果是本地的url则进行异步加载本地的Bundle
  if (scriptURL.fileURL) {
    // Reading in a large bundle can be slow. Dispatch to the background queue to do it.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSError *error = nil;
      NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:&error];
      onComplete(error, RCTSourceCreate(scriptURL, source, source.length));
    });
    return;
  }
// 启动一个下载打Task
  RCTMultipartDataTask *task = [[RCTMultipartDataTask alloc] initWithURL:scriptURL
      partHandler:^(NSInteger statusCode, NSDictionary *headers, NSData *data, NSError *error, BOOL done) {
        if (!done) {
          if (onProgress) {
            onProgress(progressEventFromData(data));
          }
          return;
        }

          // ... 处理下载的异常请见
          onComplete(error, nil);
          return;
        }

        // 对于有多个请求头的,包含X-Http-Status请求头判断其值是否为200,如果不是则直接报错
        NSString *statusCodeHeader = headers[@"X-Http-Status"];
        if (statusCodeHeader) {
          statusCode = [statusCodeHeader integerValue];
        }

        if (statusCode != 200) {
          error =
              [NSError errorWithDomain:@"JSServer"                                  code:statusCode                              userInfo:userInfoForRawResponse([[NSString alloc] initWithData:data
                                                                                    encoding:NSUTF8StringEncoding])];
          onComplete(error, nil);
          return;
        }

        // 校验服务器返回的是否为text/javascript
        NSString *contentType = headers[@"Content-Type"];
        NSString *mimeType = [[contentType componentsSeparatedByString:@";"] firstObject];
        if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"]) {
          NSString *description = [NSString              stringWithFormat:@"Expected MIME-Type to be 'application/javascript' or 'text/javascript', but got '%@'.",                               mimeType];
        // ... error初始
          onComplete(error, nil);
          return;
        }
        // 把返回的数据包装成RCTSource并返回
        RCTSource *source = RCTSourceCreate(scriptURL, data, data.length);
        parseHeaders(headers, source);
        onComplete(nil, source);
      }
      progressHandler:^(NSDictionary *headers, NSNumber *loaded, NSNumber *total) {
        // Only care about download progress events for the javascript bundle part.
        if ([headers[@"Content-Type"] isEqualToString:@"application/javascript"]) {
          onProgress(progressEventFromDownloadProgress(loaded, total));
        }
      }];
// 启动下载任务
  [task startTask];
}

果不其然,异步加载主要做了两件事情:

  1. 如果是本地的 Bundle 则使用异步加载本地的方式;
  2. 如果不是本地 Bundle 则实例化一个RCTMultipartDataTask下载任务;异步下载 Bundle;

对于 Bundle 的加载相信讲到这里大家已经了然于心;在这里简单做一个总结:

  1. RN 默认优先判断是否支持本地 Bundle 同步加载;如果可以则:
  • 判断 bundle 是否在本地,因为同步加载只加载本地 bundle;否则直接报错;
  • 使用 fopen 读取本地 bundle;
  • 通过 bundle 的前 4 个字节来判断 bundle 属于什么类型:RAMBundle , String , BCBundle;
  • 返回 bundle 的二进制数据.
  1. 否则使用异步加载 Bundle的方式
  • 如果是本地的 Bundle 则使用异步加载本地的方式;

  • 如果不是本地 Bundle 则实例化一个 RCTMultipartDataTask 下载任务;异步下载 Bundle;

  • 加载完成 Bundle 就该执行 JS 代码咯。

至此JS加载的过程已经讲解完毕,最后我们将完成的流程总结为如下图:

四、执行JS代码

一旦JavaScript代码加载完成并准备就绪,我们可以开始执行它。在React Native中,这个过程是通过RCTCxxBridge的start方法实现的。在这个方法中,JavaScript代码的执行会等待jsbundle文件加载完成,并确保原生模块也已经加载完毕,然后才会进行执行,具体的实现如下:

scss 复制代码
// RCTCxxBridge.m

// 等待 jsbundle的和native modules完成加载后则开始执行代码
 dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
  // js代码的执行
  - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync{
  // js代码执行回调
  dispatch_block_t completion = ^{

    // 当js代码执行完成,需要刷新js执行事件队列
    [self _flushPendingCalls];

    // 在主线程中通知RCTRootView; js代码已经执行完毕;当RCTRootView接收到通知就会挂载展示
    dispatch_async(dispatch_get_main_queue(), ^{
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
                                                          object:self->_parentBridge
                                                        userInfo:@{@"bridge" : self}];

      [self ensureOnJavaScriptThread:^{        // 定时器继续执行        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
      }];
    });
  };

  if (sync) {
    // 同步执行js代码
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    // 异步执行js代码
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }

  [self.devSettings setupHotModuleReloadClientIfApplicableForURL:self.bundleURL];
}

通过上面我们简单了解到:

  • 根据 sync 来选择是同步执行 js 还是异步执行 js;
  • js 执行完成之后会进入事件回调 completion,在事件回调中我们会刷新当前的 js 执行队列并发送通知给 RCTRootView;

上面讲到同步执行 js 和异步执行 js 的两个方法;enqueueApplicationScriptexecuteApplicationScriptSync,查阅源码,我们知道这两个方法都是调用的同一个方法 executeApplicationScript ;

rust 复制代码
// RCTCxxBridge.m

- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async
{
  [self _tryAndHandleError:^{
    NSString *sourceUrlStr = deriveSourceURL(url);
    // 发送 将要执行JS的通知 RCTJavaScriptWillStartExecutingNotification
    [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartExecutingNotification
                                                        object:self->_parentBridge
                                                      userInfo:@{@"bridge" : self}];

    // 如果是RAMBundle则调用_reactInstance的loadRAMBundle:方法
    // 否则调用_reactInstance的loadScriptFromString:方法
    // 还记得吗reactInstance之前中讲到的Instance初始化;
    auto reactInstance = self->_reactInstance;
    if (isRAMBundle(script)) {
      [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
      auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
      std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
      [self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad];
      [self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize];
      if (reactInstance) {
        auto registry =
            RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
        reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async);
      }
    } else if (reactInstance) {
      reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script), sourceUrlStr.UTF8String, !async);
    } else {
      std::string methodName = async ? "loadBundle" : "loadBundleSync";
      throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
    }
  }];
}

如果您看到这里是不是突然觉得豁然开朗;js 代码的执行利用的是 _reactInstance -> loadRAMBundle 或者 reactInstance -> loadScriptFromString 方法;,然而我们之前也已经讲到 reactInstance 会初始化 NativeToJsBridgeNativeToJsBridge 会利用 factory 初始化 JSIExecutor,所以我们可以认为:其实 InstanceNativeToJsBridged 的包装,NativeToJsBridge 又是 JSIExecutor 的包装;

为了证实这一点,我们一起来看一下具体的源码:(此处拿 loadScriptFromString 为例)

rust 复制代码
//Instance.cpp
void Instance::loadScriptFromString(
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL,
    bool loadSynchronously) {
  SystraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
  if (loadSynchronously) {
    // 同步加载Bundle
    loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
  } else {
    // 异步加载Bundle
    loadBundle(nullptr, std::move(string), std::move(sourceURL));
  }
}
void Instance::loadBundle(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL) {
  callback_->incrementPendingJSCalls();
  SystraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
  // 最终还是调用的NativeToJsBridge的加载方法
  nativeToJsBridge_->loadBundle(
      std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}

void Instance::loadBundleSync(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL) {
  std::unique_lock<std::mutex> lock(m_syncMutex);
  m_syncCV.wait(lock, [this] { return m_syncReady; });

    // 最终还是调用的NativeToJsBridge的加载方法
  nativeToJsBridge_->loadBundleSync(
      std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}

// NativeToJsBridge.cpp
void NativeToJsBridge::loadBundleSync(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {
  if (bundleRegistry) {
    m_executor->setBundleRegistry(std::move(bundleRegistry));
  }
  try {
    // 调用是JSIExecutor的加载方法
    m_executor->loadBundle(
        std::move(startupScript), std::move(startupScriptSourceURL));
  } catch (...) {
    m_applicationScriptHasFailure = true;
    throw;
  }
}

在 ctx 中执行 JS 源码后,会初始化 JS 环境,BatchedBridge.jsNativeModules.js 中的初始化代码也会执行。在 BatchedBridge.js 中,创建了一个名为 BatchedBridgeMessageQueue,并设置到 global_fbBatchedBridge 属性里,这个属性后面会用到。在初始化 JS 环境的时候,会加载到某些 NativeModule,这些 module 才会被初始化,即调用到 native 侧 JSINativeModulesgetModule 方法。当相关的 Module 都加载完之后,evaluateScript 方法执行完,JS 环境初始化完毕。

不知您注意到在 JSIExecutor 执行 jsbundle 之后有一个 flush 方法没?

scss 复制代码
void JSIExecutor::flush() {
  // 如果JSIExecutor的flushedQueue_函数不为空,则通过函数flushedQueue_获取待调用的方法queue,然后执行callNativeModules
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_), true);
    return;
  }

  // 通过__fbBatchedBridge作为属性key去global中取对应的值也就是batchedBridge,batchedBridge本质上是JS侧的MessageQueue类实例化的一个对象
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
  // 如果 js侧的batchedBridge对象为空(表示还未有任何Native modules被执行),那么就执行bindBridge操作; bindBridge的主要工作是将js侧的属性/方法绑定给native,以便于后续Native调用js方法
  if (!batchedBridge.isUndefined()) {
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    callNativeModules(nullptr, true);
  }
}
// 各种js方法向native的绑定
void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    // 通过js侧的__fbBatchedBridge获取对应的batchedBridge
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      throw JSINativeException(
          "Could not get BatchedBridge, make sure your bundle is packaged correctly");
    }
// 把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor对象的callFunctionReturnFlushedQueue_进行绑定
    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
  // 把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_进行绑定;
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
  // 把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_进行绑定。
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
  });
}

void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
ini 复制代码
// BatchedBridge.js
const MessageQueue = require("./MessageQueue");

const BatchedBridge: MessageQueue = new MessageQueue();

Object.defineProperty(global, "__fbBatchedBridge", {
  configurable: true,
  value: BatchedBridge,
});

module.exports = BatchedBridge;

flush 方法中的内容比较多,但总的功能如下:

  1. 在 ctx 中执行 JS 源码,会初始化 JS 环境,BatchedBridge.js, NativeModules.js 中的初始化代码也会执行。在 BatchedBridge.js 中,创建了一个名为 BatchedBridgeMessageQueue,并设置到 global__fbBatchedBridge 属性里;
  2. 如果 JSIExecutorflushedQueue 函数不为空,则通过函数 flushedQueue 获取待调用的方法 queue,然后执行 callNativeModules
  3. 通过 __fbBatchedBridge 作为属性 key 去 global 中取对应的值也就是 batchedBridgebatchedBridge 本质上是 JS 侧的 MessageQueue 类实例化的一个对象;
  4. 如果获取到的 batchedBridge 为空或者还未绑定,则先将 js 函数和 native 进行绑定,然后执行 callNativeModulescallNativeModules 的主要作用其实就是通过 invoke 的方式执行 native modules 中方法。

总结

总结起来,iOS加载React Native的过程是一个复杂而且精妙的过程,具体步骤如下。从初始化JavaScript环境到加载和渲染React Native组件,每个步骤都紧密相连,共同构建起无缝的跨平台应用体验。

  1. 初始化了 RCTBridge,并在 RCTxxBridge 中开启 JS 执行线程,所有的 js 代码都在此线程执行;
  2. 初始化所有的 native modules(包括自定义的,RN 自带的以及通过 options 参数传递的);
  3. 在 JS 线程中利用 JSExecutorFactory 创建了一个 NativeToJsBridge 实例,并利用 NativeToJSBridge 实例创建出 JSIExecutor;其中 JSIExecutor 才是整个 JS 加载初始化及执行的核心;
  4. 在初始化上述的 JSIExecutor 的同时,异步加载 JS 代码并等待初始化完成和 JS 代码加载完成;
  5. 一旦JS代码加载完成并初始化完毕,JSIExecutor 开始执行JS代码;
  6. 执行完成 js 代码后会调用 flush 方法,去调用 native modules。

通过深入了解这个过程,我们能够更好地理解iOS与React Native的交互方式,并在开发中更加高效地利用这一技术。无论是为了提升应用性能还是解决潜在的问题,对iOS加载React Native的过程进行深入研究都是非常有价值的。希望本文能为读者提供清晰的指引,帮助您们在iOS平台上构建出出色的React Native应用。

hi, 我是快手社交的 MeiLinG

快手社交技术团队正在招贤纳士🎉🎉🎉! 我们是公司的核心业务线, 这里云集了各路高手, 也充满了机会与挑战. 伴随着业务的高速发展, 团队也在快速扩张. 欢迎各位高手加入我们, 一起创造世界级的产品~ 热招岗位: Android/iOS 高级开发, Android/iOS 专家, Java 架构师, 产品经理, 测试开发... 大量 HC 等你来呦~ 内部推荐请发简历至 >>>我们的邮箱: social-tech-team@kuaishou.com <<<, 备注我的花名成功率更高哦~ 😘

相关推荐
程序员陆业聪9 天前
当APP日活过千万,客户端工程师到底在忙啥?
客户端
jyl_sh1 个月前
使用Python和Qt6创建GUI应用程序--前言
python·mvc·客户端·pyside6
万少1 个月前
HarmonyOS Next 端云一体化(3)
前端·harmonyos·客户端
童安格粉丝2 个月前
linux下安装达梦数据库v8详解
linux·数据库·centos·达梦·安装·客户端·v8
Bigger2 个月前
Tauri(三)—— 先搞定窗口配置
前端·app·客户端
Amd7943 个月前
Nuxt.js 应用中的 render:island 事件钩子
服务器·渲染·客户端·nuxt·seo·钩子·动态
MavenTalk3 个月前
Mac 环境下类Xshell 的客户端介绍
macos·xshell·客户端·iterm2·termius
jyl_sh3 个月前
WebKit(适用2024年11月份版本)
前端·浏览器·客户端·webkit
关键帧Keyframe3 个月前
音视频面试题集锦第 15 期 | 编辑 SDK 架构 | 直播回声 | 播放器架构
音视频开发·视频编码·客户端
关键帧Keyframe4 个月前
iOS 不用 libyuv 也能高效实现 RGB/YUV 数据转换丨音视频工业实战
音视频开发·视频编码·客户端