ReactNative iOS 框架源码解析

6,132 阅读32分钟

前言

虽然在跨平台这块谷歌搞了 Flutter 出来,但是从目前的生态和大厂应用上来讲,ReactNative 优势更明显些。虽然这是一个 15 年就推出的跨平台框架,但是这几年 ReactNative 团队也一直在对它进行优化,尤其在大家平时诟病的 Bridge 那块,做了很大的调整,代码也基本上从开始的 OC 形式直接改成了 cpp 的桥接,基本上达到了和 JavaScript 的无缝衔接,另外一点这样也可以做到安卓 iOS 双端代码统一。个人觉得学习它的设计思想和设计模式要远远大于它未来的生态,所以基于 0.61.0 版本,总结了一下 ReactNative 的源码,本篇作为开篇先从 ReactNative 的业务代码开始,一步步纵深探究下其内部原理。

开篇先甩张图,看一下它的大体结构,ReactNative 架构分层还是比较明显的,最上层业务层,是我们日常编写的业务组件,其中包括用 Native 编写的和用 JS 编写。中间是框架层,Bridge 部分除了 RCTBridge 和 RCTCxxBridge 之外,其余都是由 cpp 构建。最下面是 iOS 的系统库,大名鼎鼎的 JavaScriptCore,cpp 和 JSCore 进行无缝衔接,让前端的小伙伴可以直接上手 Native 开发,具体细节后面都会说,这里只是先简单的了解一下 ReactNative 的架构组成。

从ReactNative的初始化开始

根据 ReactNative 官方文档描述,当我们想要一个 ViewController 成为 RN 的容器的话,具体实现应该是这样的:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
    // 初始化rootView
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                    moduleName: @"RNHighScores"
                                             initialProperties:nil
                                                 launchOptions: nil];
    self.view = rootView;
}
  1. 根据本地 JSBundle 的 url 初始化一个 RootView。
  2. 再把这个 rootView 赋值给 VC 的 View。

在 RCTRootView 初始化方法里面还会创建一个 RCTBridge,这个 RCTBridge 就是 ReactNative 框架层中的 Bridge 部分,也是极其重要的部分

// RCTRootView.m

- (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];
}
// RCTBridge.m

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                   moduleProvider:(RCTBridgeModuleListProvider)block
                    launchOptions:(NSDictionary *)launchOptions
{
  return [self initWithDelegate:nil
                      bundleURL:bundleURL
                 moduleProvider:block
                  launchOptions:launchOptions];
}

- (void)setUp
{
  ...
  Class bridgeClass = self.bridgeClass;
  ...
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  [self.batchedBridge start];
}

- (Class)bridgeClass
{
  return [RCTCxxBridge class];
}

RCTRootView.m 内部最后会走到 RCTCxxBridge 的 -start 方法。

// RCTCxxBridge.mm

- (void)start
{
  // 1.创建一个常驻的线程
  _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                      selector:@selector(runRunLoop)
                                        object:nil];

  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();
  // 2.加载原生模块
  [self registerExtraModules];
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  [self registerExtraLazyModules];
  // 3.创建Instance实例
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;
  // 4.JSExecutorFactory工厂,生产环境它的真实类型是JSIExecutor
  // Prepare executor factory (shared_ptr for copy into block)
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {
    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 {
    id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
    executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
      if (error) {
        [weakSelf handleError:error];
      }
    }));
  }

  // 5.初始化底层Bridge
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    [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) {
  ...
  }];

  // 7.执行JS
  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];
    }
  });
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

-start 方法贯穿了整个 ReactNative 的启动流程,整个 -start 方法主要做了以下事情:

  1. 创建了个 JS 线程 _jsThread,并在内部开了个 runloop 让它长驻,初始化底层 Bridge、执行 JS 代码都是在这个线程里执行。
  2. 加载原生模块,将所有暴漏给JS使用的原生 RCTBridgeModule 初始化,每个原生模块都被初始化为 RCTModuleData,并将其仍到数组里。
  3. 初始化 Instance 实例,Instance 是公共层 Bridge 的最上层,数据公共部分的入口,到这里已经进入了 cpp 文件。Instance 是对底层 Bridge 的一层包装,提供了一些执行Call JS 的API。
  4. JSExecutorFactory 使用工厂模式,在不同场景构建出不同的 JSExecutor,其中生产环境使用 JSCExecutorFactory,用于构建 JSIExecutor,这个东西是底层 Bridge 的一个中间环节,先按住不讲后面细说。
  5. 在 JS 线程初始化底层 Bridge。
  6. 加载 JS 代码。
  7. 执行 JS 代码。

上面源码用到了一个 dispatch_group_t 类型的 prepareBridgedispatch_group_tdispatch_group_notify 联合使用保证异步代码同步按顺序执行,从上面的启动流程来看,原生模块的加载在主线程,底层 Bridge的初始化是在 JS 线程,JS 代码的加载可以同步也可以异步,这些工作都是异步执行的,dispatch_group_notify 能够保证这些工作都执行完毕,在执行 JS 代码。关于 Bridge 的构建和 JS 代码的加载执行,流程都较长下面会细说。

原生模块加载

先用一个官方文档的例子,看看原生的模块都是如何被 ReactNative 进行加载并提供给 JS 侧使用的,在 ReactNative 中创建一个原生模块,以文档日历模块为例:

// CalendarManager.h

#import <React/RCTBridgeModule.h>
@interface CalendarManager : NSObject <RCTBridgeModule>
@end
// CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTLog.h>

@implementation CalendarManager

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}

@end
  1. 创建一个 NSObject 的子类,叫 CalendarManager,并且遵循 RCTBridgeModule 协议。
  2. 在源文件内实现 RCT_EXPORT_MODULE()
  3. 使用 RCT_EXPORT_METHOD 宏,实现需要给 JS 导出的方法。

这样一个原生模块就创建完了,可以让 JS 侧直接调用:

// index.js

import {NativeModules} from 'react-native';
const CalendarManager = NativeModules.CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');

这样一个简单的调用就结束了,我们只知道这样调用就能把参数传递到 CalendarManager里面来,然而并不知道 ReactNative背后做了什么,知其然不知其所以然 那我们深入看下源码,先看看 RCT_EXPORT_MODULE() 做了什么。

这是一个宏的嵌套函数,层层展开如下:

// RCTBridgeModule.h

RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

void RCTRegisterModule(Class moduleClass)
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
    RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
  });

  RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
            @"%@ does not conform to the RCTBridgeModule protocol",
            moduleClass);

  // Register module
  dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
    [RCTModuleClasses addObject:moduleClass];
  });
}

展开宏自动帮我们实现了三个函数,@# 的意思是自动把宏的参数 js_name 转成字符通过 +moduleName 返回原生模块名称,重写 + (void)load 函数,调用 RCTRegisterModule() 把类注册到原生模块类集合。App在启动后,实际上所有模块都走了 +load 方法,也就是说都调用了 RCTRegisterModule() 方法进行注册。RCTRegisterModule() 里面首先判断注册的模块是否遵循了 RCTBridgeModule 协议,如果遵循了则会放入到 RCTModuleClasses 数组中,这里处于数组线程安全考虑,使用栅栏函数加了读写锁,其实还有个读函数后面会说。

第一个宏说完了再来看看第二个宏,RCT_EXPORT_METHOD 都做了些啥。RCT_EXPORT_METHOD 也是个展开宏,但是比上面的要复杂些,展开如下:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
//展开如下
 + (const RCTMethodInfo *)__rct_export__390 {
   static RCTMethodInfo config = {
      "",
      "addEvent:(NSString *)name location:(NSString *)location",
      NO,
   };
   return &config;
 }
 - (void)addEvent:(NSString *)name location:(NSString *)location
 {
   RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
 }

看展开结果,RCT_EXPORT_METHOD 宏首先帮我们补全了 - (void),使得 - (void) 加上参数还原了我们上面给 JS 定义的方法,还声成了一个以 __rct_export__ 拼接上 __LINE____COUNTER__ 为方法名的函数,这是两个C语言宏,分别代表着行号与一个内置计数器,大概意思是要生成一个唯一的标识。该函数返回了一个 RCTMethodInfo 类型的对象, 包含了导出函数信息,有 JS 名、原生函数名、是否为同步函数。

这些方法定义好后,是如何被 JS 所加载的,那我们继续深入源码,探究一下 CalendarManager 原生模块,是如何被 ReactNative 加载并被 JS 调用到的。

回到 -start 方法的第三步,加载原生模块:

// RCTCxxBridge.mm

- (void)start
{
  dispatch_group_t prepareBridge = dispatch_group_create();
  // 加载手动导出的原生模块
  [self registerExtraModules];
  // 加载自动导出的原生模块
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  // 加载调试模式所需原生模块
  [self registerExtraLazyModules];
  ...
}

我们以自动注册的模块为例,探究下具体实现部分:

// RCTCxxBridge.mm

- (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses
                                        lazilyDiscovered:(BOOL)lazilyDiscovered
{
  RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
                          @"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);

  NSArray *moduleClassesCopy = [moduleClasses copy];
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
  for (Class moduleClass in moduleClassesCopy) {
    if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
      continue;
    }
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);

    // Check for module name collisions
    RCTModuleData *moduleData = _moduleDataByName[moduleName];
    if (moduleData) {
      ...省略一些代码
    }
    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];

    _moduleDataByName[moduleName] = moduleData;
    [_moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
  }
  [_moduleDataByID addObjectsFromArray:moduleDataByID];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");

  return moduleDataByID;
}

这里面主要创建了三个表:

  1. _moduleDataByID数组:通过 RCTGetModuleClasses() 将前面注册到数组中的模块取出来,循环遍历创建RCTModuleData类型对象,存储到_moduleDataByID数组中。实际上RCTModuleData把原生模块包装了一层,原生模块的实例只是RCTModuleData中的一个属性。这里不仅实例化了原生模块,还做了一些其他事情,这个后面会说
  2. _moduleDataByName字典:key就是前面提到的,模块导出宏自动添加的 +moduleName 返回值,如果没有设置,默认为当前类名。
  3. _moduleClassesByID数组:将所有模块的Class装进数组。

到这里所有原生模块就都被 ReactNative 加载并初始化完毕。这些原生模块都是JS侧通过Bridge来进行调用,JSBridge 应该都是耳熟能详的东西了,专门用来做 JS 和原生进行交互使用的工具,不管 Cordova框架 也好还是我前面几篇文章说的 WKJavaScriptBridge 也好,都是使用的这种桥接技术,万变不离其宗,ReactNative 也是通过 JSBridge 来做交互的,只不过是个 cpp 版的 bridge。这个 bridge 的构建还得从上面的 -start 说起。-start 函数在第五步里面初始化了底层Bridge,我们在深入探究下底层 Bridge 的初始化流程。

底层Bridge构建

-start 中关于底层 bridge 构建的相关代码如下:

// RCTCxxBridge.mm

- (void)start
{
  // 创建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
  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();
  ...
  
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;

  ...
  
  dispatch_group_enter(prepareBridge);
  // 在_jsThread中初始化底层bridge
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];
  ...
}
  1. -start 的时候,创建了一个 JS 线程 _jsThread,这里涉及到线程保活的知识,子线程默认不开启 runloop,子线程在执行完任务后会被释放掉,所以这里在子线程开启了个 runloop,让线程一直存活,底层 Bridge 初始化,js bundle 包的加载/运行都在 JS 线程执行。
  2. _jsThread 线程中构建底层bridge。

继续看 _initializeBridge: 都做了什么:

// RCTCxxBridge.mm

- (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];
    }
  });
  ...
  [self _initializeBridgeLocked:executorFactory];
}

- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  _reactInstance->initializeBridge(
                                   std::make_unique<RCTInstanceCallback>(self),
                                   executorFactory,
                                   _jsMessageThread,
                                   [self _buildModuleRegistryUnlocked]);
}
  1. 创建一个名为 _jsMessageThread 线程,绑定了 _jsThread 的 runloop,使其任务执行完毕不退出,被 RCTCxxBridge 持有,传递给底层 bridge。
  2. 初始化底层bridge,这是一个 C++ 函数,函数内部初始化了一个叫 NativeToJsBridge 的实例。
// 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) {
  callback_ = std::move(callback);
  moduleRegistry_ = std::move(moduleRegistry);
  jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
    nativeToJsBridge_ = std::make_unique<NativeToJsBridge>(
        jsef.get(), moduleRegistry_, jsQueue, callback_);

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

  CHECK(nativeToJsBridge_);
}

根据代码不难看出,最主要的工作是在上面传进来的线程中,同步执行 NativeToJsBridge 的创建,nativeToJsBridge_ 的创建传递了三个参数:

  1. InstanceCallback:底层调用结束后给上层的回调。

  2. JSExecutorFactory:生产环境实际为 JSIExecutor,后面这个类会细说。

  3. MessageQueueThread:上层传递过来的 _jsMessageThread

  4. ModuleRegistry:它负责两个非常核心的任务:一是生成中间版本的原生模块配置信息,进一步加工就可以最终导入给 JS 端,二是作为JS call Native的中转站。

ModuleRegistry

上面说了 moduleRegistry 中包括了所有native module信息,具体还要回到 RCTCxxBridge.mm 看代码:

// RCTCxxBridge.mm
- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked
{
  ...

  auto registry = std::make_shared<ModuleRegistry>(
         createNativeModules(_moduleDataByID, self, _reactInstance),
         moduleNotFoundCallback);
         
  ...
  return registry;
}
//RCTCxxUtils.mm

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

_buildModuleRegistryUnlocked 主要负责构建并返回一个 ModuleRegistry 实例,_moduleDataByID 正是前面初始化原生模块时候的 RCTModuleData 数组,这里遍历数组,将 RCTModuleData 模块生成对应的 cpp 版本的 NativeModule,在 ModuleRegistry 的构造函数里,这些 NativeModule 又会被 ModuleRegistry 所持有:

ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback)
    : modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {}

这个 modules_ 至关重要,将来 JS 侧想要获取 Native 侧提供的各种配置信息都要靠它来获取。既然 ModuleRegistry 里面装的都是 NativeModule,那再看看 NativeModule 职能。NativeModule 定义了一套接口用于获取原生模块信息、调用原生模块函数:

// NativeModule.h
class NativeModule {
 public:
  virtual ~NativeModule() {}
  //获取模块信息
  virtual std::string getName() = 0;
  virtual std::vector<MethodDescriptor> getMethods() = 0;
  virtual folly::dynamic getConstants() = 0;
  //调用原生函数
  virtual void invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) = 0;
  virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic&& args) = 0;
};

NativeModule 只提供了接口并没有实现,我把它理解成为一个接口,真正的实现是在 RCTNativeModule,实际上就是个 C++ 版本的原生模块描述类,它是对 OC 版本 RCTModuleData 的封装,是面向底层 cpp 层 bridge 的。

获取模块信息,我们以 getConstants 为例,看名字也能看出来,这是在获取模块的常量信息。我们刨根问题往下走看看它是怎么拿到常量信息的:

// RCTNativeModule.mm

folly::dynamic RCTNativeModule::getConstants() {
  ...
  NSDictionary *constants = m_moduleData.exportedConstants;
  folly::dynamic ret = convertIdToFollyDynamic(constants);
  return ret;
}

m_moduleData 就是我们 OC 版本的原生模块信息 RCTModuleData,我们继续往下走:

// RCTModuleData.mm

- (NSDictionary<NSString *, id> *)exportedConstants
{
  [self gatherConstants];
  NSDictionary<NSString *, id> *constants = _constantsToExport;
  _constantsToExport = nil; // Not needed anymore
  return constants;
}
- (void)gatherConstants
{
  //如果有常量导出并且是第一次导出
  if (_hasConstantsToExport && !_constantsToExport) {
    RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil);
    //确保原生模块存在
    (void)[self instance];
    //因为整个bridge的构建和js与native的通信都是在jsthread中进行,所以如果需要在主线程获取constantsToExport需要切换回主线程
    if (_requiresMainQueueSetup) {
      if (!RCTIsMainQueue()) {
        RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass);
      }

      RCTUnsafeExecuteOnMainQueueSync(^{
        self->_constantsToExport = [self->_instance constantsToExport] ?: @{};
      });
    } else {
      _constantsToExport = [_instance constantsToExport] ?: @{};
    }
    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  }
}

经过上面的步骤我们不难发现,我们的原生module被 RCTModuleData 包了一层,RCTModuleData 又被 RCTNativeModule 包了一层,RCTNativeModule 又都装到了 ModuleRegistry 里,实际上 ModuleRegistry 也同样被更底层的 JSINativeModules 持有着,而 JSINativeModules 又被更更底层的 JSIExcutor 持有着,CalendarManager <- RCTModuleData <- RCTNativeModule <- ModuleRegistry <- JSINativeModules <- JSIExcutor, JSIExcutor 会在执行 JS 代码的时候向 JS 侧注入一个叫 NativeModuleProxy 的对象,JS 侧调用到了这个对象,就会调用到Native侧的 JSIExcutor 内,这样我们原生模块的信息是如何提供给 JS 侧使用的路线就出来了。后面还会详细的说 JSIExcutor NativeModuleProxy JSINativeModules。对 ReactNative源码没有过了解的同学可能有点迷糊,不过不用担心,后面说到 NativeModuleProxy 的时候,我会再用函数调用栈的形式再来一遍。

至此我们所有原生的模块信息都已经准备完毕,目前来看所有的原生模块都注册到了 ModuleRegistry 中,那不妨猜想以下,是不是只要 JS 调用了 ModuleRegistry 就可以执行原生模块的方法了?事实确实如此,要想理解这个调用过程,需要先了解一下 NativeToJsBridgeNativeToJsBridge 是一 个C++ 类,前面说到的 Instance 是对 NativeToJsBridge 的一层包装,NativeToJsBridgeInstance 更接近底层。

NativeToJsBridge

NativeToJsBridge 顾名思义是 Native Call JS 的集大成者,那么它和JS Call Native有什么关系呢,实际上它内部还持有着 JSExecutorJsToNativeBridge,并将 JsToNativeBridge 最终传递给 JSIExecutor,由 JSIExecutor 来触发JS Call Native。实际 Native Call JS 底层也是 JSIExecutor 实现,这个 JSIExecutor 具体是做什么的一直没说,这个先按住不说,后面再说,先看看 NativeToJsBridge 的定义:

// NativeToJsBridge.cpp

class NativeToJsBridge {
public:
  friend class JsToNativeBridge;

  // 必须在主线程调用
  NativeToJsBridge(
      JSExecutorFactory* jsExecutorFactory,
      std::shared_ptr<ModuleRegistry> registry,
      std::shared_ptr<MessageQueueThread> jsQueue,
      std::shared_ptr<InstanceCallback> callback);
  virtual ~NativeToJsBridge();
  
  // 传入module ID、method ID、参数用于在JS侧执行一个函数
  void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args);
  
  // 通过callbackId调用JS侧的回调
  void invokeCallback(double callbackId, folly::dynamic&& args);
  
  // 开始执行JS application. 如果bundleRegistry非空,就会使用RAM的方式 读取JS源码文件
  // 否则就假定 startupCode 已经包含了所有的JS源码文件
  void loadApplication(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupCode,
    std::string sourceURL);
  void loadApplicationSync(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupCode,
    std::string sourceURL);

private:
  std::shared_ptr<JsToNativeBridge> m_delegate;
  std::unique_ptr<JSExecutor> m_executor;
  std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
};

因为在模块加载那里举的例子并没有包含Native给JS的回调,实际上从上面的代码能看出来,如果有回调的话会触发 NativeToJsBridge 的 invokeCallback 方法,把 callbackId 回调给JS侧,JS 侧拿到 callbackId 就可以执行相应的回调了。先简单介绍下它的成员变量和方法:

成员变量

  1. m_delegate:JsToNativeBridge类型的引用,主要用于JS call Native
  2. m_executor:这个是在前面说的工厂创建出来的类,生产环境是JSIExecutor,调试环境是RCTObjcExecutor,这是底层通信的集大成者,不管是JS Call Native还是Native Call JS都是基于它来进行。
  3. m_executorMessageQueueThread:并非 NativeToJsBridge 自己初始化,而是作为初始化参数传递进来的,是最外层的 _jsMessageThread。NativeToJsBridge 在被 Instance 初始化的时候传递进来的,JS 侧和 Native 侧交互都是在这个线程处理。

主要方法

  1. void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args); 这个函数的意义就是通过module ID和method ID以及参数去调用JS方法

  2. void invokeCallback(double callbackId, folly::dynamic&& args); 这个函数的意义就是通过callbackId和参数触发一个JS的回调。通常是JS call Native method之后,native把一些异步的执行结果再以callback的形式回调给JS。

  3. void loadApplication( std::unique_ptr bundleRegistry, std::unique_ptr startupCode, std::string sourceURL); 这个方法的作用是执行JS代码,他还有一个兄弟叫做 loadApplicationSync,顾名思义,他兄弟是一个同步函数,所以他自己就是异步执行JS代码。

构造函数

// 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()) {}

NativeToJsBridge 内部把原生信息 registry 和外部传入的 callback 作为入参生成了 JsToNativeBridge 并持有, 这也是 JsToNativeBridge 能够 Call Native 的原因,jsExecutorFactory 又通过JsToNativeBridge 和外部传入的js线程生成了一个 executor 并持有,在生产环境下,这个executor就是 JSIExecutor。JS Call Native 实际上就是 JSIExecutor 来调用 JsToNativeBridge 实现,JSIExecutor 上面也有提到一直按住没表,那我们再来看下JSIExecutor。

JSIExecutor

通过上面的流程,我们明白了 JSIExecutor 相比 NativeToJsBridge 显得更底层,我们可以理解为 InstanceNativeToJsBridge 的包装,NativeToJsBridgeJSIExecutor 的包装,实际上在 Instance 中调用 callJSFunction:,它的调用顺序应该是这样的:Instance->NativeToJsBridge->JSIExecutorJSIExecutor 会调用更底层,这个后面说。同样JS想要调用Native,也是一样:JSIExecutor->JsToNativeBridge->ModuleRegistry。之前说了,在 NativeToJsBridge 的构造函数中 jsExecutorFactory 使用 JsToNativeBridge 实例 m_delegate 和 jsQueue 创建了 m_executor。这里我们主要以生产环境的JSIExecutor为例介绍。调试模式下请参考RCTObjcExecutor,他们都继承自 JSExecutor。下面是两种环境下 executor 的创建方式,生产环境的 JSIExecutor 通过 JSCExecutorFactory 生产,如下:

// JSCExecutorFactory.mm

 return std::make_unique<JSIExecutor>(
      facebook::jsc::makeJSCRuntime(),
      delegate,
      JSIExecutor::defaultTimeoutInvoker,
      std::move(installBindings));

了解了它的由来后我们再简要分析下 JSIExecutor 里面的几个关键属性:

// JSIExecutor.h

class JSIExecutor : public JSExecutor {
  ...省略很多代码
  std::shared_ptr<jsi::Runtime> runtime_;
  std::shared_ptr<ExecutorDelegate> delegate_;
  JSINativeModules nativeModules_;
  ...
};
  1. jsi::Runtime runtime_:实际上就是JSCRuntime,内部实现了Runtime的接口,提供了可以执行JS的能力,内部比较复杂,这是基于C语言版的JavaScriptCore实现,用于创建JS上下文,执行JS,像JS注入原生对象等功能。
  2. delegate_:这个delegate是在NativeToJsBridge中初始化JSIExecutor是传递进来的参数,这个参数正是JsToNativeBridge对象,负责JS Call Native。
  3. nativeModules_:由外部传入的 ModuleRegistry 构造而成,在JS Call Native的时候,会通过 JSINativeModules 里面的 ModuleRegistry 来获取原生模块信息,并把这个信息通过 __fbGenNativeModule 函数传递给 JS 侧,由此可见,原生模块信息并不是主动导入到 JS 侧的,而是 JS 侧到原生获取的,大致流程如下,首先从 Native 侧的 getModule 开始:
// JSINativeModules.cpp

Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
  ...
  // 调用createModule方法
  auto module = createModule(rt, moduleName);
  ...
  auto result =
      m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  return Value(rt, result->second);
}

getModule 内部调用了 createModule,那么 createModule 方法做了什么呢:

// JSINativeModules.cpp

folly::Optional<Object> JSINativeModules::createModule(
    Runtime& rt,
    const std::string& name) {
  ...
  if (!m_genNativeModuleJS) {
  // 获取JS侧全局函数__fbGenNativeModule
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }
 // 根据模块名,去原生模块注册对象中取出模块信息
  auto result = m_moduleRegistry->getConfig(name);
  ...
  // 调用__fbGenNativeModule并把原生模块信息传递过去
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,
      valueFromDynamic(rt, result->config),
      static_cast<double>(result->index));
  ...

  return module;
}
  1. 获取JS侧全局函数 __fbGenNativeModule,用以调用这个函数可以调用到 JS 侧,这个函数的具体细节,后面会讲。
  2. 根据模块名,去 ModuleRegistry 中取出模块信息,ModuleRegistry 的定义上面讲过了,所有原声模块的信息都存在这里。
  3. 调用 __fbGenNativeModule 这个全局函数并把从 ModuleRegistry 取出来的原生模块信息传递过去。

那么 getModule 是哪里调用的呢?

NativeModuleProxy

NativeModuleProxy 是 getModule 的唯一入口:

// JSIExecutor.cpp

class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  // 构造函数 JSIExecutor实例作为NativeModuleProxy构造函数的入参
  NativeModuleProxy(JSIExecutor &executor) : executor_(executor) {}
  
  // NativeModuleProxy 的 get方法 用于获取native module信息
  Value get(Runtime &rt, const PropNameID &name) override {
    return executor_.nativeModules_.getModule(rt, name);
  }
};

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

// JSIExecutor.cpp 

void JSIExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {

  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(*this)));

  // ...
  
}

runtime 是一个 JSCRuntime 类型对象,通过调用 rumtime_->global() 获得一个全局的 global 对象。然后又通过 setProperty 方法给 global 对象设置了一个名为 nativeModuleProxy 的对象。JS 侧的 global 对象通过 "nativeModuleProxy" 这个名字即可访问到 native 侧的 NativeModuleProxy。本质上,JS侧的 global.nativeModuleProxy 就是native侧的 nativeModuleProxy。换句话说,我们在 JS 侧的 NativeModules 对应的就是 native 侧的 nativeModuleProxy。我们不妨再深入看以下 JS 侧的源码:

let NativeModules: {[moduleName: string]: Object, ...} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  invariant(
    bridgeConfig,
    '__fbBatchedBridgeConfig is not set, cannot invoke native modules',
  );
  ...
}

我们在写 ReactNative 代码时使用的 NativeModules 正是原生端的 nativeModuleProxy 对象,文章开头的例子 NativeModules.CalendarManager,实际上都会先到 Native 侧的 nativeModuleProxy,再到 JSINativeModules,再调用到 getModule 方法,通过 CalendarManager 模块名来获取 CalendarManager 的信息,然后 Call 全局的 JS 函数 __fbGenNativeModule,把原生信息传递给 JS 侧。

原生模块信息导出

下面我们再以函数调用栈的形式,走一遍 CalendarManager 导出常量的流程,也可以理解为 JS 侧获取 Native 侧信息的流程,方法的导出同理:

  • NativeModules-CalendarManager (JS侧)
  • NativeModuleProxy-get (进入Native侧)
    • JSINativeModules-getModule
    • JSINativeModules-createModule
      • ModuleRegistry-getConfig
        • RCTNativeModule-getConstants
          • RCTModuleData-exportedConstants
            • CalendarManager-constantsToExport (进入自定义的CalendarManager)

我们可以顺便再分析下 JS 侧拿到 Native 侧的配置信息后做了什么:

// react-native/Libraries/BatchedBridge/NativeModules.js
// 生成原生模块信息
function genModule(
  config: ?ModuleConfig,
  moduleID: number,
): ?{name: string, module?: Object} {

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

  const module = {};
  // 添加 JS版原生模块函数
  methods &&
    methods.forEach((methodName, methodID) => {
      const isPromise =
        promiseMethods && arrayContains(promiseMethods, methodID);
      const isSync = syncMethods && arrayContains(syncMethods, methodID);
      const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
      // 生成JS函数
      module[methodName] = genMethod(moduleID, methodID, methodType);
    });
  // 添加原生模块导出常量
  Object.assign(module, constants);
  if (module.getConstants == null) {
    module.getConstants = () => constants || Object.freeze({});
  } else {
    ...
  }
  return {name: moduleName, module};
}

// 导出genModule到全局变量global上以便native可以调用
global.__fbGenNativeModule = genModule;

// 生成函数
genMethod(moduleID: number, methodID: number, type: MethodType) {
  let fn = null;
  if (type === 'promise') {
    fn = function(...args: Array<any>) {
      return new Promise((resolve, reject) => {
        // 函数入队
        BatchedBridge.enqueueNativeCall(
          moduleID,
          methodID,
          args,
          data => resolve(data),
          errorData => reject(createErrorFromErrorData(errorData)),
        );
      });
    };
  } else if (type === 'sync') {
    fn = function(...args: Array<any>) {
      return global.nativeCallSyncHook(moduleID, methodID, args);
    };
  } else {
    fn = function(...args: Array<any>) {
      ...
      BatchedBridge.enqueueNativeCall(...);
    };
  }
  fn.type = type;
  return fn;
}

最后这个 return {name: moduleName, module} 会返回到原生端,原生端会取出 module,再将它构造成 JS 对象给 JS 使用:

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"));

到这里 JSIExecutor 的初始化完成了,JSBridge 就算搭建完了,以后 Native call JS 都会先后经由 Instance、NativeToJSBridge、JSIExecutor最终到达JS。

加载JS

分析到这里,原生模块已全部加载完毕,也已将所有原生模块信息导出给 JS 侧使用,RCTCxxBridge 到 _reactInstance 到 instance 背后的 NativeToJsBridge、JSIExecutor 构建 Bridge 的整个流程也已经全部走完。但是最后一步,JS是怎么调用到原生和原生怎么给JS回调的还并未展开,我们先回到 RCTCxxBrige 的 -start 方法:

- (void)start
{
  // 异步加载 js bundle包
    [self loadSource:^(NSError *error, RCTSource *source) {
    if (error) {
      [weakSelf handleError:error];
    }

    sourceCode = source.data;
    dispatch_group_leave(prepareBridge);
  } onProgress:^(RCTLoadingProgress *progressData) {
  // 展示加载bundle 的 loadingView 
  ...
  }];
}

// 等待native moudle 和 JS 代码加载完毕后就执行JS
  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];
    }
  });
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
  ...
    __weak RCTCxxBridge *weakSelf = self;
    [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) {
      if (error) {
        [weakSelf handleError:error];
        return;
      }
      onSourceLoad(error, source);
    }];
}

+ (void)loadBundleAtURL:(NSURL *)scriptURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)onComplete
{
  // 尝试同步加载
  int64_t sourceLength;
  NSError *error;
  NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL
                                          runtimeBCVersion:JSNoBytecodeFileFormatVersion
                                              sourceLength:&sourceLength
                                                     error:&error];
  if (data) {
    onComplete(nil, RCTSourceCreate(scriptURL, data, sourceLength));
    return;
  }
  ...
  // 同步加载失败异步加载
  if (isCannotLoadSyncError) {
    attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
  } else {
    onComplete(error, nil);
  }
}

static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoadProgressBlock onProgress, RCTSourceLoadBlock onComplete)
{
  scriptURL = sanitizeURL(scriptURL);

  if (scriptURL.fileURL) {
    // 异步加载
    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;
  }
}

实际上 JS 代码的加载和底层 Bridge 的创建,都是并发执行的,因为 dispatch_group 的缘故,只有在他们都执行完毕和原生模块初始化完毕后,才会执行 JS。

执行JS

终于到了最后异步,执行JS代码,其实就是把js bundle包经过层层传递,最终交给 JavaScriptCore 去执行。

/ RCTCxxBridge.mm
- (void)start
{
   ...
  // 等待”原生模块实例创建完毕、底层Bridge初始化完毕、js bundle包加载完毕“,在JS线程执行js源码
  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];
    }
  });
}

最后通过executeSourceCode执行代码,executeSourceCode 源码如下:

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
  // JS bundle包执行完毕回调
  dispatch_block_t completion = ^{
    // 执行暂存的Native call JS
    [self _flushPendingCalls];
    dispatch_async(dispatch_get_main_queue(), ^{
      // 主线程发送通知
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];
      // 开启定时任务,最终用于驱动JS端定时任务
      [self ensureOnJavaScriptThread:^{
        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
      }];
    });
  };
  // 根据sync来选择执行JS的方式(同步、异步)
  if (sync) {
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }
}

上述代码主要做了两件事情:

  1. 构建回调,在JS执行完进行回调。
  2. 执行 JS 代码。
- (void)enqueueApplicationScript:(NSData *)script
                             url:(NSURL *)url
                      onComplete:(dispatch_block_t)onComplete
{
  // 底层转化为在JS线程执行JS bundle
  [self executeApplicationScript:script url:url async:YES];
  if (onComplete) {
    _jsMessageThread->runOnQueue(onComplete);
  }
}

由于底层js bundle最终会在JS线程执行,因此 _jsMessageThread->runOnQueue(onComplete) 可以保证先执行完 JS bundle,后执行 onComplete 回调。

- (void)executeApplicationScript:(NSData *)script
                             url:(NSURL *)url
                           async:(BOOL)async
{
  [self _tryAndHandleError:^{
    NSString *sourceUrlStr = deriveSourceURL(url);
    ...
    // 根据 async 来同步加载、异步加载
    self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),sourceUrlStr.UTF8String, !async);
  }];
}
// Instance.cpp
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                    std::string sourceURL,
                                    bool loadSynchronously) {
    ...
    loadApplication(nullptr, std::move(string), std::move(sourceURL));
}

void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                               std::unique_ptr<const JSBigString> string,
                               std::string sourceURL) {
  nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string),
                                     std::move(sourceURL));
}
// NativeToJsBridge.cpp
void NativeToJsBridge::loadApplication(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {

  // 派发到JS线程执行js bundle
  runOnExecutorQueue( [...] (JSExecutor* executor) mutable {
      ...
      executor->loadApplicationScript(std::move(*startupScript), std::move(startupScriptSourceURL));
  });
}

如上,loadApplicationloadApplicationSync 这两个方法实现基本一致,都是调用了成员变量 m_executor的loadApplicationScript 方法,区别在于 loadApplication 把代码放到了 m_executorMessageQueueThread 中去执行,而 loadApplicationSync 在当前线程执行。

// JSIexecutor.cpp

void JSIExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {

  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(*this)));

  runtime_->global().setProperty(
      *runtime_,
      "nativeFlushQueueImmediate",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
          1,
          [this](
              jsi::Runtime &,
              const jsi::Value &,
              const jsi::Value *args,
              size_t count) {
            callNativeModules(args[0], false);
            return Value::undefined();
          }));

  runtime_->global().setProperty(
      *runtime_,
      "nativeCallSyncHook",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
          1,
          [this](
              jsi::Runtime &,
              const jsi::Value &,
              const jsi::Value *args,
              size_t count) { return nativeCallSyncHook(args, count); }));
  // 最终调用到JavaScriptCore的JSEvaluateScript函数
  runtime_->evaluateJavaScript(
      std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
      
  flush();
}
  1. 通过 setProperty 方法给 global 对象设置了 nativeModuleProxy 的对象。JS 侧的 NativeModules 对应的就是 native 侧的 nativeModuleProxy。
  2. 向 global 中注入了 nativeFlushQueueImmediate,nativeCallSyncHook 两个方法。
  3. 调用 runtime_->evaluateJavaScript 方法,最终调用到 JavaScriptCore 的 JSEvaluateScript 函数,SEvaluateScript 的作用就是在 JS 环境中执行 JS 代码。
  4. JS 脚本执行完成,执行 flush 操作。flush 函数的主要作用就是执行 JS 侧的队列中缓存的对 native 的方法调用。
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");
  if (!batchedBridge.isUndefined()) {
    // 绑定batchedBridge
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    // 如果没有获取到JS侧定义的batchedBridge对象,则直接执行callNativeModules方法,即没有bind操作。
    callNativeModules(nullptr, true);
  }
}

BatchedBridge:批处理桥,ReactNative 在处理 JS 和 Native 通信并非调用一次执行一次,而是将调用消息保存到队列,在适当的实际进行flush。

flush()

flush 函数有必要着重讲一下,在 javaScript 代码被执行完毕后,会马上执行 flush() 函数,flush 内会先判断 flushedQueue_ 是否存在,flushedQueue_ 如果存在,就会直接执行 callNativeModules 调用原生模块的方法,如果不存在,就去 JS 侧获取 batchedBridgebatchedBridge 是什么,js 侧有个 BatchedBridge.js,如下:

// BatchedBridge.js

const MessageQueue = require('./MessageQueue');

const BatchedBridge: MessageQueue = new MessageQueue();

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

module.exports = BatchedBridge;

如果 JS 调用过 Native,BatchedBridge 就会被初始化,BatchedBridge 对象实际上就是 JS 侧的 MessageQueue,在初始化完之后,定义了一个叫 __fbBatchedBridge 的全局属性,并把 BatchedBridge 对象作为这个属性的 value 值,等待着被 JSIExecutor 使用。

再回到 flush() 方法里面,native 侧通过 __fbBatchedBridge 拿到 batchedBridge,先判断它是否存在,如果不存在,说明 JS 从来没有调用过 Native,callNativeModules 直接传空,如果存在,就说明 JS 侧有调用,Native 侧就需要进行绑定 JSBridge 的方法,将 JS 的调用队列拿过来进行执行。因为在 JS 被加载的过程中,可能会存在 JS 调用 Native,所以这里提前执行一次 flush,将这些调用全部执行。

bindBridge()

flush()另一个重要的地方就是 bindBridge:

// JSIExecutor.cpp

void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    SystraceSection s("JSIExecutor::bindBridge (once)");
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      throw JSINativeException(
          "Could not get BatchedBridge, make sure your bundle is packaged correctly");
    }

    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
    callFunctionReturnResultAndFlushedQueue_ =
        batchedBridge.getPropertyAsFunction(
            *runtime_, "callFunctionReturnResultAndFlushedQueue");
  });
}

bindBridge一共绑定了四个方法:

  • callFunctionReturnFlushedQueue
  • invokeCallbackAndReturnFlushedQueue
  • flushedQueue
  • callFunctionReturnResultAndFlushedQueue

故名思义,这几个方法都是由 native 触发,并把 JS 侧的 flushedQueue 返回给 Native 侧进行处理。以 callFunctionReturnFlushedQueue 为例,Native 侧的 callFunctionReturnFlushedQueue_ 指针指向了 JS 侧的 callFunctionReturnFlushedQueue 方法,当调用 callFunctionReturnFlushedQueue_ 的时候会直接调用到 JS 侧,JS 侧的方法定义如下:

// MessageQueue.js

callFunctionReturnFlushedQueue(
    module: string,
    method: string,
    args: any[],
  ): null | [Array<number>, Array<number>, Array<any>, number] {
    this.__guard(() => {
      this.__callFunction(module, method, args);
    });

    return this.flushedQueue();
  }
// MessageQueue.js

 flushedQueue(): null | [Array<number>, Array<number>, Array<any>, number] {
    this.__guard(() => {
      this.__callImmediates();
    });

    const queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

JS 会先根据 moduleId 和 methodId 完成对 JS 侧业务的调用,然后会执行 flushedQueue 函数将队列清空,在将其返回给 Native 侧,Native 侧拿到队列就会继续执行 JSIExecutor->callNativeModules->ModuleRegistry 那一串流程了。至此整个 JS 执行流程全部走完,开始执行 completion(),代码又回到 RCTCxxBridge.mm 的 executeSourceCode:sync: 内:

// RCTCxxBridge.mm

dispatch_block_t completion = ^{
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];
       ...
    });
  };

然后 bridge 会向外发送一个名为 RCTJavaScriptDidLoadNotification 的通知,这个通知在哪里实现呢,可以全局搜一下我们发现我们又回到了最顶端,RCTRootView 里。

// RCTRootView.m

- (void)javaScriptDidLoad:(NSNotification *)notification
{
  ...
  [self bundleFinishedLoading:bridge];
  ...
}

- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
  ...
  [self runApplication:bridge];
  ...
}

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };

  RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}

最后执行到 runApplication,runApplication 内部会调用 enqueueJSCall,AppRegistry 是组件名,runApplication 是组件的方法,args 是传递的参数,接着会走 RCTCxxBridge->Instance->NativeToJsBridge->JSIExecutor->callFunctionReturnFlushedQueue_ 这一大长串,callFunctionReturnFlushedQueue_ 实际上就是 JS 侧的 callFunctionReturnFlushedQueue 方法,JS 侧开始执行入口渲染界面,完事返回 flushedQueue 给 Native,Native 再去遍历 queue 根据 moduleId、methodId 去执行原生模块的方法。至此,整个交互流程结束。原生模块的加载,底层 Bridge 的构建,JS代码的加载、执行,全部分析完毕。基于上面的分析,再总结一下完整的 JS 和 Native 的交互过程。

Native Call JS

Native 到 JS 的流程相对来说比较简单,上面基本分析的八九不离十,下面再详细的过一遍这个流程,Native To JS 还是以上面的 runApplication: 为例:

// RCTRootView.m
- (void)runApplication:(RCTBridge *)bridge
{
... 
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}
// RCTCxxBridge.mm

- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
  ...
  __weak __typeof(self) weakSelf = self;
  [self _runAfterLoad:^(){
    RCTProfileEndFlowEvent();
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) {
      return;
    }

    if (strongSelf->_reactInstance) {
      strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
                                             convertIdToFollyDynamic(args ?: @[]));
   ...
}

_runAfterLoad 用来判断js bundle是否加载完毕,如果已经加载完毕就直接回调 block,然后调用 Instance 的 callJSFunction,否则会将 block 先存起来等 js bundle 加载完毕再调用。这里其实是个知识点,如果当你遇到了这样的场景,你的调用需要等待另一个异步任务执行完,如果知识简单的按照顺序执行或许 GCD 就可以搞定,但是像这种指令批量发送的,要怎么处理?可以完全参考 ReactNative 在这一块堪比教科书的处理方式。废话少说继续往下走:

// Instance.cpp

void Instance::callJSFunction(
    std::string &&module,
    std::string &&method,
    folly::dynamic &&params) {
  callback_->incrementPendingJSCalls();
  nativeToJsBridge_->callFunction(
      std::move(module), std::move(method), std::move(params));
}

Instance 作为 NativeToJsBridge 的中转继续往下走:

// NativeToJsBridge.cpp

void NativeToJsBridge::callFunction(module, method, arguments) {
  runOnExecutorQueue([...](JSExecutor* executor) {
      executor->callFunction(module, method, arguments);
    });
}
// JSIExecutor.cpp

void JSIExecutor::callFunction(
    const std::string &moduleId,
    const std::string &methodId,
    const folly::dynamic &arguments) {
  SystraceSection s(
      "JSIExecutor::callFunction", "moduleId", moduleId, "methodId", methodId);
  if (!callFunctionReturnFlushedQueue_) {
    bindBridge();
  }

  // Construct the error message producer in case this times out.
  // This is executed on a background thread, so it must capture its parameters
  // by value.
  auto errorProducer = [=] {
    std::stringstream ss;
    ss << "moduleID: " << moduleId << " methodID: " << methodId
       << " arguments: " << folly::toJson(arguments);
    return ss.str();
  };

  Value ret = Value::undefined();
  try {
    scopedTimeoutInvoker_(
        [&] {
          ret = callFunctionReturnFlushedQueue_->call(
              *runtime_,
              moduleId,
              methodId,
              valueFromDynamic(*runtime_, arguments));
        },
        std::move(errorProducer));
  } catch (...) {
    std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
  }

  callNativeModules(ret, true);
}

最终会执行到 callFunctionReturnFlushedQueue_ ,callFunctionReturnFlushedQueue_ 上面已经分析过了,调用 callFunctionReturnFlushedQueue_ 相当于直接调用到了 MessageQueue.jscallFunctionReturnFlushedQueue 函数。 callFunctionReturnFlushedQueue 上面页已经分析过了,JS 侧会根据参数直接调用到 JS 侧的业务模块,执行业务代码,至此,Native To JS 的整个流程执行完毕,接下来再分析下 JS To Native 的过程。

JS Call Native

经过上面的分析,已经非常清楚,JS 侧在执行 NativeModules.CalendarManager 的时候,JS 侧会先到 Native 侧获取 RCTCalendarManager 模块导出的所有信息,然后会调用 MessageQueue.jsenqueueNative() 方法,将这次调用和 callback 回调都存储在 MessageQueue 队列中,并不会立即执行,这是 ReactNative 对 Bridge 交互优化的一个手段,暂时先不分析后面会说。在 Native 侧调用 callFunctionReturnFlushedQueue 的时候,JS 处理完业务代码会执行一次 flushedQueue,flushedQueue 的整个过程上面都有分析,迷糊了的同学可以往上翻。JS 侧执行 flushedQueue 后,会将存储在队列里面的所有调用指令,全部返回给 Native 侧,Native 侧拿到 MessageQueue 后,就会走前面说的那一长串流程,最后到达 Native 业务侧,Native 处理完业务,如果需要回调,会继续执行 enqueueCallback:,最后到 JS 侧,JS 侧取出前面存储的 callback 回调,然后回调到 JS 的业务代码,整个流程结束。这里没有贴源码,因为上面基本分析差不多了。最后再分析下,JS 侧的调用为什么要先存储在 MessageQueue 中而不是直接调用,这还要从 JS 侧的 flushQueue 说起。

flushedQueue

JS 桥接让 JS 和 Native 可以直接交互的同时,也会带来性能损耗的问题。尤其像 ReactNative 这样的框架,JS 与 Native 的交互非常频繁,可以想象 scrollView 的滚动,动画的实现等等,将会带来非常大的性能开销。flushedQueue 虽然不能完美的解决这个问题,但是也优化到了极致。JS Call Native 并不是直接调用的 Native 方法,而是将调用消息先存放到队列:

// MessageQueue.js

const MIN_TIME_BETWEEN_FLUSHES_MS = 5;

enqueueNativeCall(
    moduleID: number,
    methodID: number,
    params: any[],
    onFail: ?Function,
    onSucc: ?Function,
  ) {
    this.processCallbacks(moduleID, methodID, params, onFail, onSucc);

    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);
    this._queue[PARAMS].push(params);

    const now = Date.now();
    if (
      global.nativeFlushQueueImmediate &&
      now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
    ) {
      const queue = this._queue;
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
      global.nativeFlushQueueImmediate(queue);
    }
   
  }

可以看到 _queue 这个变量 push 进去的模块名和方法名都是 ID,并不是真正的类型,实际上 Native 侧存了一个映射表,拿到 JS 侧传过来的 ID 映射到具体的类、方法,通过 NSInvocation 组装参数发送消息。至于为什么传 ID 过去,目的是减少通信数据量,降低沟通成本。另外JS侧把这些数据存放到 _queue 后并没有主动发给 Native 侧,而是在适当的时机 Native 过来取,正是通过 bindBridge() 里面绑定的方法。那有同学可能会问了,这个时机是什么时候?在我们日常开发中,往往都是只有事件响应了,才会做代码执行,消息传递,这个事件可能是屏幕点击事件,也就可能是 timer 事件、系统事件等等,ReactNative 也是一样,它的所有 UI 页面都是 Native 实现,当 Native 有事件触发就会Call JS,也可能是主动调用 JS,这有可能会更新 UI,也有可能是单纯出发个事件例如按钮的点击,待 JS 侧处理完业务逻辑后会执行 flushQueue,将消息队列返回给 Native 侧。这样做最主要的目的还是为了减少沟通成本,提升桥接性能,这一点与 Cordova 框架的设计几乎一致。压缩数据大小加上降低交互频次,几乎可以说是优化到极限了

如果 Native 始终不调用 JS 侧是不是队列里面的消息就一直不会被执行?上面源码的 MIN_TIME_BETWEEN_FLUSHES_MS 的常量不知道大家有没有注意到,每次 enqueueNativeCall 的时候都会拿上一次清空 queue 的时间(flushQueue 或者 invokeCallback 或者执行 nativeFlushQueueImmediate 都会重置这个时间)和现在比较,如果 JS call Native 批量调用时间间隔 >= 5毫秒,那就执行一次 nativeFlushQueueImmediate(),这个函数是干嘛的之前没有说,我也是追着 JS 侧的源码才发现这个函数。这也是一个全局函数,也是 JS 侧唯一主动调用 Native 侧的方法,其他 JS 调用 Native 都是被动等 Native 过来取。然后我在全局搜索了以下,最终发现这个方法是在 JSIExecutor 进行 loadApplicationScript 的时候给 JS 侧注入的,JS 侧调用这个方法,Native 侧就会执行 callNativeModules()把消息分发到各个模块。

到这里,ReactNative 的核心代码已经过的差不多了,后续会着手对 ReactNative UI 渲染层面上继续分析,分析它是如何动态修改我们的 UI 组件。