详解React Native初始化和通信机制

致敬

开始准备写这一篇文章的时候,中国的新型冠状病毒肺炎疫情还在继续,累积确诊81058人,现存确诊10827人。这篇文章写完的时候,累计确诊81501人,现存确认5846人。疫情已经持续了3个月,但也终将过去。毫无疑问,在这漫长的3个月的时间里,很多与疫情抗争的工作人员都是非常辛苦的,再次感谢&致敬。

前言

这是一篇原理性文章,也是一篇源码分析文章。这篇文章是笔者学习RN源码过程中的一篇记录文章,主要记录了程序从启动之初到开始执行JS源码的整个流程。从AppDelegate的application:didFinishLaunchingWithOptions:说起,全流程涉及到关键类的初始化工作和JavaScript的执行以及JS&Native之间的通信。围绕bridge的初始化、JS源码的加载、JS源码的执行、Native调用JS、JS调用Native展开分析。内容虽然很长,但其实很浅,大部分都是源码,并没有加入自己太多的思考,耐心看完就可以理解。

本文篇幅很长的原因是笔者贴出了大量的RN源码。文章中的源码已经做了精简,如果想看完整的代码还是建议参考RN源码。笔者主要删除了源码中与逻辑无强关联的代码。比如debug环境的宏、锁、调试相关的代码、健壮性相关的代码、错误处理相关的代码、代码执行耗时相关的代码。删除这些代码不会影响对源码的阅读和理解,请大家放心。

阅读这篇文章你最好具备以下条件:你应该是一个iOS开发者,本文是站在一个iOS工程的角度分析RN的源码,当然如果你能看懂Objective-C代码也是可以的。你应该对RN有所了解,最好是使用RN开发过一些需求。你应该对JS有所了解,本文会涉及少量JS代码。最后,你最好具备一些C++的知识,RN源码中存在大量的C++代码,不需要会写,了解C++语法能看懂C++代码即可。当然,如果你认为万物皆对象,以上条件都可以忽略,那么让我们开始吧》》》

本文基于React Native 0.61.5进行分析

名词

本文中主要涉及到以下几个类:RCTBridge、RCTCxxBridge、Instance、NativeToJsBridge、JsToNativeBridge、JSIExecutor、RCTRootView。他们的关系大概如下(JSIExecutor是本文涉及到的最内层的类):

React Native关键类关系图

开始

我们新建一个名为NewProject的RN的iOS工程。可以看出AppDelegate.m的application: didFinishLaunchingWithOptions:方法实现是这样的:

- (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:@"NewProject"
                                            initialProperties:nil];

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  // 3.设置rootViewController
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

以上代码可以看出,application:didFinishLaunchWithOptions:主要做了3件事:

1.初始化一个RCTBridge实例

2.再用RCTBridge实例初始化一个rootView

3.用rootView配置一个rootViewController

第三步用rootView初始化一个rootViewController没什么可说的,本片文章我们主要窥探初始化RCTBridge和RCTRootView。

RCTBridge初始化

RCTBridge初始化是重点也是难点,虽然叫RCTBridge的初始化,但实际上不仅仅是初始化一个RCTBridge实例那么简单,在其背后还有RCTCxxBridge、NativeToJSBridge、JSExecutor(JSIExecutor生产环境/RCTObjcExecutor调试环境)、JsToNativeBridge的初始化,这里仅作为一个了解,不必纠结,后面会详细介绍。先来看一下appDelegate中调用的RCTBridge的初始化的源码实现:

// RCTBridge.m
- (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;
  
  // 只有delegate返回的bundleURL发生变化才更新_bundleURL
  NSURL *previousDelegateURL = _delegateBundleURL;
  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
    _bundleURL = _delegateBundleURL;
  }
  
  // 初始化self.batchedBridge,也就是RCTCxxBridge
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  
  // 启动RCTCxxBridge
  [self.batchedBridge start];

}

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

上面RCTBridge的初始化方法是创建了一个RCTBridge实例,通过调用私有方法setUp对bridge进行配置。setUp主要做了2件事情:

1.初始化self.batchedBridge,也就是RCTCxxBridge实例

2.启动self.bathedBridge(RCTCxxBridge实例)

self.batchedBridge的start源码如下:

// RCTCxxBridge.mm

- (void)start {

  // 1.提前设置并开启JS线程 _jsThread
  _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                      selector:@selector(runRunLoop)
                                        object:nil];
  _jsThread.name = RCTJSThreadName; // @"com.facebook.react.JavaScript"
  _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();

  // 2.初始化注册native module
  [self registerExtraModules];
  // 初始化所有不能被懒加载的native module
  [self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  [self registerExtraLazyModules];

  _reactInstance.reset(new Instance);
  __weak RCTCxxBridge *weakSelf = self;

  // 准备executor factory
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {
    if ([self.delegate conformsToProtocol:@ (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];
      }
    }));
  }
  
  // 3.module初始化完就初始化底层Instance实例,也就是_reactInstance
  // 在JS线程初始化_reactInstance、RCTMessageThread、nativeToJsBridge、JSCExecutor
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

  // 4.异步加载JS代码
  dispatch_group_enter(prepareBridge);
  __block NSData *sourceCode;
  [self loadSource:^(NSError *error, RCTSource *source) {
    sourceCode = source.data;
    dispatch_group_leave(prepareBridge);
  }];

  // 5.等待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];
    }
  });
}

这个start方法很关键,是RCTCxxBridge中最主要的方法。因为做的事情较多且掺杂着部分C++代码, 所以让人觉得很复杂,梳理一下,其实start中主要做了5件事:

1.创建并开启一个JS线程(_jsThread),该线程绑定了一个runloop,顾名思义,这个线程就是用来执行JS代码的,后续所有的js代码都是在这个线程里执行。

2.初始化注册所有要暴露给js调用的native module,每个native module类都封装成一个RCTModuleData实例,如果需要在主线程中创建某些类的实例,则会在主线程中去创建实例。这些RCTModuleData会分别存储在字典和数组里。

3.准备JS和Native之间的桥和JS运行环境,初始化JSExecutorFactory实例(顾名思义,JSExecutorFactory是一个JSExecutor的工厂,也就是负责生产JSExecutor实例的工厂),然后在JS线程中创建JS的RCTMessageThread,初始化_reactInstance(Instance实例)、nativeToJsBridge(NativeToJsBridge实例)、executor(JSIExecutor实例)。以上这些事情主要是在_initializeBridge:方法中完成的,此处作为了解,后面详细分析。

4.异步加载JS源码

5.native module和JS代码都加载完毕后就执行JS代码

以上提到了三个C++类Instance、NativeToJsBridge、JSIExecutor,他们都是native call JS的桥梁。但有什么区别呢?其实他们的关系是Instance->NativeToJsBridge->JSIExecutor。即Instance中创建并持有NativeToJsBridge实例,NativeToJsBridge中又创建并持有JSIExecutor实例。换句话说,Instance是对NativeToJsBridge的封装,NativeToJsBridge是对JSIExecutor的封装。

上述源码里用到一个叫prepareBridge的dispatch_group_t,虽然名称叫prepareBridge,但其实是一个dispatch_group_t。dispatch_group_t和dispatch_group_notify联合使用保证异步代码同步按顺序执行,也就是被添加到group中的任务都做完了之后再执行notify中的任务(但group中的多个任务的执行顺序是无序的)。有很多初始化工作是异步并行的,运行JS源码是在所有准备工作之后才能进行,所以用了dispatch_group_t和dispatch_group_notify机制来确保这个问题。

在上述源码里我们看到了一个名为ensureOnJavaScriptThread:的方法,如下:

// RCTCxxBridge.mm

  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

看名字就知道,ensureOnJavaScriptThread:是RCTCxxBridge里专门将block放在JS线程中执行的方法。他的目的就是确保待执行的block能在JS线程执行,所以他接收一个block作为参数,然后判断当前线程是否是先前创建的JS线程,如果是则立即在JS线程同步执行block,否则切换到JS线程执行block。源码如下:

// RCTCxxBridge.mm

- (void)ensureOnJavaScriptThread:(dispatch_block_t)block
{
  if ([NSThread currentThread] == _jsThread) {
    [self _tryAndHandleError:block];
  } else {
    [self performSelector:@selector(_tryAndHandleError:)
          onThread:_jsThread
          withObject:block
          waitUntilDone:NO];
  }
}

大家常说JS是单线程的,在RN里就是这样的。native侧创建了一个专门服务于JS的线程,然后绑定了一个runloop不让这个JS线程退出,后续JS代码都是在这个线程里执行。

刚才上面只是穿插介绍了ensureOnJavaScriptThread的作用。回过头来看start方法中ensureOnJavaScriptThread:的block主要是在JS线程执行了weakSelf _initializeBridge:executorFactory; 接下来看下_initializeBridge:到底做了哪些事情,源码如下:

// RCTCxxBridge.mm

- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  RCTAssertJSThread();
  __weak RCTCxxBridge *weakSelf = self;
  _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
    if (error) {
      [weakSelf handleError:error];
    }
  });
  if (_reactInstance) {
    [self _initializeBridgeLocked:executorFactory];
  }
}

- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{

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

如上,通过assert不难看出,RCTCxxBridge的 _initializeBridge:方法确实是在JSThread中调用的。且 _initializeBridge:方法主要做了2件事:

1.创建一个名为_jsMessageThread的RCTMessageThread实例,并被RCTCxxBridge持有(可以看出messageThread实际上是由runloop实现的)

2.调用_initializeBridgeLocked:传入RCTExecutorFactory初始化bridge(nativeToJsBridge实例)

_initializeBridgeLocked:的实现更简单,_initializeBridgeLocked:内部调用了_reactInstance的initializeBridge方法继续初始化bridge(NativeToJsBrige实例)。如注释所述,这个方法是异步调用的,但是所有经由_reactInstance实例对JS方法的调用都会被Instance中名为m_syncReady这个成员变量给锁住。

此处先忽略上面的第四个参数self _buildModuleRegistryUnlocked ,继续看_reactInstance的initializeBridge方法:

// 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_ = folly::make_unique<NativeToJsBridge>(
        jsef.get(), moduleRegistry_, jsQueue, callback_);

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

不难看出,在jsQueue(MessageQueueThread)线程里最主要创建了NativeToJsBrige实例,即nativeToJsBridge_。然后instance持有了这个nativeToJsBridge_以及其他2个外部传进来的参数 callback、moduleRegistry。至此Instance的初始化就结束了。下面我们来看一下这四个参数:

第一个参数是InstanceCallback类型的回调,用于底层执行结束后往上层回调,实际上调用方传递的是self,即RCTCxxBridge实例。

第二个参数是JSExecutorFactory,即生产JSExecutor的工厂实例。Instance内部会使用这个facotry获得一个JSExecutor实例。

第三个参数就是我们在外面创建的MessageQueueThread。

第四个参数moduleRegistry是ModuleRegistry类型的实例。moduleRegistry里面包含了所有的Native Module信息,即RCTModuleData。即将前面生成的所有RCTModuleData传给了_reactInstance。至此我们知道self _buildModuleRegistryUnlocked实际上是返回了一个RCTModuleRegistry实例。

下面简单介绍后3个参数:

JSExecutorFactory

JSExecutorFactory,顾名思义用于生产JSExecutor实例,JSExecutor用于执行JS,也是JS和Native之间的桥梁。无论是Native call JS还是JS call Native,JSExecutor都起到了至关重要的作用。生产环境下使用的是JSCExecutorFactory,返回JSIExecutor用于执行JS,开发环境使用的是RCTObjcExecutorFactory,返回RCTObjcExecutor通过websocket链接chrome执行JS。

// JSExecutor.h

class JSExecutorFactory {
public:
  virtual std::unique_ptr<JSExecutor> createJSExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> jsQueue) = 0;
  virtual ~JSExecutorFactory() {}
};

MessageQueueThread

MessageQueueThread类型对象用于提供队列执行。这里是由RCTMessageThread来实现,内部用的是CFRunLoop来实现。

除RCTMessageThread之外,另一个实现是DispatchMessageQueueThread,我们不做详细介绍。

// MessageQueueThread.h

namespace facebook {
namespace react {

class MessageQueueThread {
 public:
  virtual ~MessageQueueThread() {}
  virtual void runOnQueue(std::function<void()>&&) = 0;
  virtual void runOnQueueSync(std::function<void()>&&) = 0;
  virtual void quitSynchronous() = 0;
 };
}}

ModuleRegistry

上面说了moduleRegistry中包括了所有native module信息,即RCTModuleData。这还要从我们刚才忽略的self _buildModuleRegistryUnlocked方法说起,_buildModuleRegistryUnlocked方法主要负责构建一个RCTModuleRegistry实例并返回,如下:

// RCTCxxBridge.mm

- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked
{
  __weak __typeof(self) weakSelf = self;
  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.instance就是native module实例对象
        [moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },
        std::make_shared<DispatchMessageQueueThread>(moduleData)));
    } else {
      nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge, moduleData));
    }
  }
  return nativeModules;
}

可以看出,上面使用_moduleDataByID初始化并返回了一个ModuleRegistry实例。_moduleDataByID是一个含有若干个RCTModuleData对象的数组。在https://cloud.tencent.com/developer/article/1597259中我们介绍了_moduleDataByID中的RCTModuleData实际上就是在程序启动后load各个class的时候被收集的。有必要提一下,上面源码中的moduleData.instance其实就是native module的实例对象。通过如下代码可窥见一斑:

// RCTModuleData.mm

- (instancetype)initWithModuleClass:(Class)moduleClass
                             bridge:(RCTBridge *)bridge
{
  return [self initWithModuleClass:moduleClass
                    moduleProvider:^id<RCTBridgeModule>{ return [moduleClass new]; }
                            bridge:bridge];
}

- (instancetype)initWithModuleClass:(Class)moduleClass
                     moduleProvider:(RCTBridgeModuleProvider)moduleProvider
                             bridge:(RCTBridge *)bridge
{
  if (self = [super init]) {
    _bridge = bridge;
    _moduleClass = moduleClass;
    _moduleProvider = [moduleProvider copy];
    [self setUp];
  }
  return self;
}

- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
                                bridge:(RCTBridge *)bridge
{
  if (self = [super init]) {
    _bridge = bridge;
    _instance = instance;
    _moduleClass = [instance class];
    [self setUp];
  }
  return self;
}

至此,RN中Instance实例(_reactInstance)的初始化已经介绍完了,接下来介绍NativeToJSBridge。为什么要介绍NativeToJsBridge?因为我们上面说了Instance是对NativeToJSBridge的封装,就像UIView是对CALayer的封装一样,可见NativeToJSBridge比Instance更加接近底层(实际JSExecutor比NativeToJSBridge更加底层,我们后面详细的说明)。

NativeToJSBridge

NativeToJsBridge的主要作用是负责管理所有native对JS的调用,并且也管理了executor们和他们的线程。NativeToJsBridge的所有函数可以在任意线程被调用。除非某些方法是为了同步加载Application Script,否则所有的方法都是在JSQueue线程排队等候执行的,且这些函数会被立即返回。这也说明大部分Native call JS的方法都是在jsQueue这个线程执行的,而jsQueue实际上就是messageQueueThread。

在上面Instance::initializeBridge函数中,我们知道Instance创建了一个名为nativeToJsBridge_的NativeToJSBridge的实例并被Instance实例持有。如下:

// 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_成员变量
    nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
        jsef.get(), moduleRegistry_, jsQueue, callback_);
  });
}

可以看出nativeToJsBirdge的类型是NativeToJsBridge。下面我们来看下NativeToJsBridge类的具体定义。

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

如上,NativeToJsBridge是一个C++类,它主要有如下3个重要的成员变量:

  • <JsToNativeBridge> m_delegate

    m_delegate是JsToNativeBridge类型的引用,主要用于JS call Native

  • <JSExecutor> m_executor

    JSExecutor类型引用,主要用于执行Native call JS,实际上生产环境使用的是JSIExecutor;调试环境使用的是RCTObjcExecutor

  • <MessageQueueThread> m_executorMessageQueueThread

    MessageQueueThread类型引用,内部是由runloop实现的,由外部传递,用于队列管理。这里的外部传递是指m_executorMessageQueueThread并非NativeToJsBridge自己初始化的,而是作为初始化NativeToJsBridge的参数由上层传递进来的。如果你还记得NativeToJsBridge是在Instance::initializeBridge中初始化的,那么你就知道这个m_executorMessageQueueThread最终起源于RCTCxxBridge中的_jsMessageThread,进而由一步一步的函数调用,穿山越岭传递进来的。所以,NativeToJsBridge的m_executorMessageQueueThread就是 了RCTCxxBridge的_jsMessageThread

除了以上3个关键的属性之外,NativeToJsBridge还定义了4个函数:

  • void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args);

    这个函数的意义就是通过module ID和method ID以及参数去调用JS方法

  • void invokeCallback(double callbackId, folly::dynamic&& args);

这个函数的意义就是通过callbackId和参数触发一个JS的回调。通常是JS call Native method之后,native把一些异步的执行结果再以callback的形式回调给JS。

  • void loadApplication(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupCode,
    std::string sourceURL);

    这个方法的作用是执行JS代码,他还有一个兄弟叫做loadApplicationSync,顾名思义,他兄弟是一个同步函数,所以他自己就是异步执行JS代码。

NativeToJsBridge构造函数

上面说了NativeBridge的3个关键属性和4个关键方法,接下来我们说下他的构造函数,接触过C++的开发者应该知道,C++中类的构造函数和类同名,如下是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()) {}

上面我们在Instance::initializeBridge中就已经调用过NativeToJsBridge的构造函数创建了一个NativeToJsBridge实例并被赋值给Instance实例的成员变量nativeToJsBridge_。在NativeToJSBridge构造函数的后面有一个初始化列表,其中registry和callback作为入参生成了一个JsToNativeBridge类型实例赋值给m_delegate。jsExecutorFactory又通过m_delegate和jsQueue生产了一个executor赋值给m_executor(m_delegate最终是给m_executor使用的,在生产环境下,jsQueue对于executor也是无用的),m_executorMessageQueueThread最后指向了jsQueue。

JSIExecutor

和Instance、NativeToJsBridge一样,JSIExecutor主要用来Native call JS,但他是比Instance和NativeToJsBridge更深层次的一个核心类,换句话说,我们可以把NativeToJsBridge理解为JSIExecutor的包装(而Instance又是对NativeToJsBridge的包装),对Instance的调用最终都会走到NativeToJsBridge,对NativeToJsBridge的调用最终都会走到JSIExecutor,比如getJavaScriptContext、callFunction、invokeCallback这些方法。他们的调用顺序是Instance->NativeToJsBridge->JSIExecutor。上面我们说了,在NativeToJsBridge的构造函数中jsExecutorFactory使用JsToNativeBridge实例m_delegate和jsQueue创建了m_executor(实际上生产环境下只用了m_delegate)。这里我们主要以生产环境的JSIExecutor为例介绍。调试模式下请参考RCTObjcExecutor,他们都继承自JSExecutor。下面是两种环境下executor的创建方式,生产环境的JSIExecutor通过JSCExecutorFactory生产,调试模式下的RCTObjcExecutor通过RCTObjcExecutorFactory生产,如下:

// JSCExecutorFactory.mm

std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> __unused jsQueue) {
    
  return folly::make_unique<JSIExecutor>(
      facebook::jsc::makeJSCRuntime(),
      delegate,
      JSIExecutor::defaultTimeoutInvoker,
      std::move(installBindings));
}

// RCTObjcExecutor.mm
std::unique_ptr<JSExecutor> RCTObjcExecutorFactory::createJSExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> jsQueue) {
  return std::unique_ptr<JSExecutor>(
    new RCTObjcExecutor(m_jse, m_errorBlock, jsQueue, delegate));
}

JSIExecutor主要的几个关键属性:

  • <jsi::Runtime> runtime_

Runtime类型指针,代表JS的运行时。这是一个抽象类,其实际上是由JSCRuntime来实现的。JSCRuntime实现了<jsi::Runtime>接口,提供了创建JS上下文的功能,同时可以执行JS。如下是JSCRuntime的evaluateJavaScript方法实现:

// JSCRuntime.cpp

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;
  JSValueRef res = JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
 
  return createValue(res);
}
  • <ExecutorDelegate> delegate_

ExecutorDelegate类型的指针,这里的ExecutorDelegate是抽象类,实际是由JsToNativeBridge实现的。也即JSIExecutor引用了JsToNativeBridge实例。还记得NativeToJsBridge中的JsToNativeBridge类型的成员变量m_delegate吗?其实这里的delegate_就是NativeToJsBridge中的m_delegate。

  • <JSINativeModules> nativeModules_

JSINativeModules由上层传入的ModuleRegistry构造而成,同时会将ModuleRegistry中包含的本地模块配置信息通过”__fbGenNativeModule”保存到JS端。

JSINativeModules有个getModule方法,getModule方法内又调用了 createModule方法,createModule方法生成了module信息,源码如下:

// JSINativeModules.cpp

Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
  std::string moduleName = name.utf8(rt);
  // 调用createModule方法
  auto module = createModule(rt, moduleName);
  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) {

  if (!m_genNativeModuleJS) {
    // runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
    // JS端的函数__fbGenNativeModule调用最终就会走到这里。
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }

  auto result = m_moduleRegistry->getConfig(name);
  // 调用m_genNativeModuleJS函数,即__fbGenNativeModule
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,
      valueFromDynamic(rt, result->config),
      static_cast<double>(result->index));

  folly::Optional<Object> module(
      moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
  // 返回生成的module
  return module;
}

那到这里你会问JSINativeModules负责createModule并提供了可以访问某个module的接口getModule。那是谁调用的JSINativeModules的getModule呢?全局搜索getModule不难发现,getModule是在一个名为NativeModuleProxy的get方法里调用的,如下:

// 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这个C++类又是在哪里使用的呢?全局搜索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_->global().setProperty( //... ); runtime是一个JSCRuntime类型对象,通过调用rumtime_->global()获得一个全局的global对象。然后又通过setProperty方法给global对象设置了一个名为nativeModuleProxy的对象。日后(JS侧的)global对象通过"nativeModuleProxy"这个名字即可访问到(native侧的)NativeModuleProxy,这听起来像是一句废话。说到这里,我们不得不说一下JS侧的global.nativeModuleProxy,我们会诧异于在native侧和JS侧的global中都存在nativeModuleProxy变量,其实这不是巧合,本质上,JS侧的global.nativeModuleProxy就是native侧的nativeModuleProxy。换句话说,我们在JS侧的NativeModules对应的就是native侧的nativeModuleProxy。JS侧代码如下:

// 源码位置:react-native/Libraries/BatchedBridge/NativeModules.js

let NativeModules: {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
  const bridgeConfig = global.__fbBatchedBri

通过上述JS代码可以看出,JS侧的NativeModules == JS侧的global.nativeModuleProxy == native侧NativeModuleProxy。

JS侧对NativeModules的调用都会经由native侧的NativeModuleProxy后进而调用到JSINativeModules的的createModule这个实例方法返回了moduleName对应的module config。值得一提的是,在createModule方法中,还调用了一个名为“__fbGenNativeModule”的JS方法,如下:

// JSINativeModules.cpp
// JSINativeModules::createModule方法中
    
  if (!m_genNativeModuleJS) {
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }
  
  Value moduleInfo = m_genNativeModuleJS->call(
    rt,
    valueFromDynamic(rt, result->config),
    static_cast<double>(result->index));
    

通过runtime获取JS全局对象global,接着通过方法名“__fbGenNativeModule”从golbal实例中获取这个JS方法指针,然后进行调用。当然,要想在native侧可以调用到这个JS方法,前提是需要在JS侧对这个方法进行定义。如下是这个方法在JS侧的源码实现:

// 源码位置: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 = {};
  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);
    });

  return {name: moduleName, module};
}

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

上面JS源码先是定义了一个名为genModule的函数,然后又把这个函数挂载到了全局变量global的"__fbGenNativeModule"上,以便作为一个可以全局访问的全局函数,这样做的目的是方便在native侧调用。所以,__fbGenNativeModule这个函数指代的就是gemModule。

到这里JSIExecutor的初始化完成了,这样和JS之间的桥梁就建好了,以后Native call JS都会先后经由Instance、NativeToJSBridge、JSIExecutor最终到达JS。

下图描述了bridge初始化的方法调用时序图和涉及到的主要类:

RN bridge初始化

加载JS代码

然后,我们不要忘了,以上这一大段篇幅只是初始化了RCTCxxBridge中的_reactInstance以及instance背后的NativeToJsBridge、JSIExecutor。我们还记得RCTCxxBridge的start方法中,除了初始化_reactInstance、NativeToJSBridge、JSIExecutor之外,与之同时进行的还有加载JS源码,jsBundle的加载是通过RCTJavaScriptLoader进行的。当初始化工作和JS源码加载都完成后,就会执行JS源码。让我们来回顾一下RCTCxxBridge.mm中的start方法:

// RCTCxxBridge.mm

- (void)start {

  // 此处省略若干行...
  
  // 1. 初始化native module
  dispatch_group_t prepareBridge = dispatch_group_create();
  
  // 2. 在JS线程初始化_reactInstance、RCTMessageThread、nativeToJsBridge、JSCExecutor
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];
  
  // 3. 异步加载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) {
   // 展示加载bundle 的 loadingView 
#if RCT_DEV && __has_include(<React/RCTDevLoadingView.h>)
    RCTDevLoadingView *loadingView = [weakSelf moduleForName:RCTBridgeModuleNameForClass([RCTDevLoadingView class])
                                       lazilyLoadIfNecessary:NO];
    [loadingView updateProgress:progressData];
#endif
  }];

  // 4. 等待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];
    }
  });
}

很明显,在初始化_reactInstance完成之后,还有异步load JS源码以及执行源码的工作(实际上,因为dispatch_group_t的原因,初始化_reactInstance和load JS源码是并发执行的,但只有在两者工作都完毕后才去执行JS代码)。本节我们将会介绍JS代码的加载。

// RCTCxxBridge.mm

- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
  // 发送通知 将要加载JS代码
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge];
  // JS代码加载完成的回调
  RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) {

    NSDictionary *userInfo = @{
      RCTBridgeDidDownloadScriptNotificationSourceKey: source ?: [NSNull null],
      RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey: self->_bridgeDescription ?: [NSNull null],
    };

    [center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo];

    _onSourceLoad(error, source);
  };
  // 通知delegate
  if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) {
    [self.delegate loadSourceForBridge:_parentBridge onProgress:onProgress onComplete:onSourceLoad];
  } else if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
    [self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad];
  } else {
    // 加载JS代码
    __weak RCTCxxBridge *weakSelf = self;
    [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) {
      onSourceLoad(error, source);
    }];
  }
}

可以看出,上述代码中通过调用RCTJavaScriptLoader的类方法loadBundleAtURL:onProgress:onComplete加载JS bundle。如下是源码:

// RCTJavaScriptLoader.mm

+ (void)loadBundleAtURL:(NSURL *)scriptURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)onComplete
{
  int64_t sourceLength;
  NSError *error;
  // 尝试 同步加载JS bundle
  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;
  // 尝试 异步加载JS bundle
  if (isCannotLoadSyncError) {
    attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
  } else {
    onComplete(error, nil);
  }
}

不难看出,上面一段代码还是很清晰的,主要做了2件事:

1.尝试同步加载JS bundle,加载成功就执行onComplete回调

2.如果不能同步加载则尝试异步加载JS bundle,否则直接onComplete

那么什么情况下可以同步加载JS bundle?答案是如果要加载的bundle是本地预置的或是已经下载好的,那么就可以同步加载,否则只能异步download。

下面我们分别来看下同步加载bundle和异步加载bundle的实现。

同步加载bundle

// RCTJavaScriptLoader.mm

+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
                               runtimeBCVersion:(int32_t)runtimeBCVersion
                                   sourceLength:(int64_t *)sourceLength
                                          error:(NSError **)error
{
  // 如果bundle不在本地,那么就不能同步加载
  if (!scriptURL.fileURL) {
    if (error) {
      *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
                                   code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
                               userInfo:@{NSLocalizedDescriptionKey:
                                            [NSString stringWithFormat:@"Cannot load %@ URLs synchronously",
                                             scriptURL.scheme]}];
    }
    return nil;
  }
  
  // 检查前4个字节来判断这个bundle是普通bundle还是RAM bundle
  // 如果是RAM bundle,则在前4个字节有一个数字 `(0xFB0BD1E5)`
  // RAM bundle相对于普通bundle的好处是当有需要时再去以“懒加载”的形式2把modules注入到JSC
  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
  // 关于fread各个参数的解释: 
  // __ptr -- 这是指向带有最小尺寸 size*nitems 字节的内存块的指针。
  // __size -- 这是要读取的每个元素的大小,以字节为单位。
  // __nitems -- 这是元素的个数,每个元素的大小为 size 字节。
  // __stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
  facebook::react::BundleHeader header;
  size_t readResult = fread(&header, sizeof(header), 1, bundle);
  fclose(bundle);

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

  struct stat statInfo;
  if (sourceLength) {
    *sourceLength = statInfo.st_size;
  }
  
  return [NSData dataWithBytes:&header length:sizeof(header)];
}

如上,主要做了3件事:

1.如果bundle不在本地,那么就不能同步加载,写入error

2.检查前4个字节来获取这个bundle类型,类型信息存在ScriptTag中

3.返回bundle data

异步加载bundle

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

// RCTJavaScriptLoader.mm

static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoadProgressBlock onProgress, RCTSourceLoadBlock onComplete)
{
  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;
  }

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

    // 验证服务器返回的是不是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 = [NSError errorWithDomain:@"JSServer"
                                  code:NSURLErrorCannotParseResponse
                              userInfo:@{
                                         NSLocalizedDescriptionKey: description,
                                         @"headers": headers,
                                         @"data": data
                                       }];
      onComplete(error, nil);
      return;
    }
    // 把data包装成source对象
    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];
}

如上,不难看出,异步加载主要做了2件事情:

1.如果bundle是本地文件则异步加载本地bundle

2.如果不是本地bundle则开启一个RCTMultipartDataTask异步下载

以上,是RN所有加载JS代码的逻辑。接下来介绍native是如何执行JS代码的。

执行JS代码

执行JS代码,终于走到这一步了,还是要从RCTCxxBridge的start方法说起,如下:

// RCTCxxBridge.mm

- (void)start
{
  // 此处省略若干行...
  // Wait for both the modules and source code to have finished loading
  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];
    }
  });
}

不难看出,start方法在最后通过调用executeSourceCode:执行JS代码,executeSourceCode:源码如下:

// RCTCxxBridge.mm

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
  dispatch_block_t completion = ^{
    // 在主线程上执行状态更新和通知,这样我们就不会遇到RCTRootView的时序问题
    dispatch_async(dispatch_get_main_queue(), ^{
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];
    });
  };
  
  // 根据sync来选择执行JS的方式(同步、异步)
  if (sync) {
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }
}

如上,代码主要做了两件事:

1.准备一个completion的block,在JS执行完成后回调

2.根据sync来选择是同步执行还是异步执行JS

通过看这两个函数的实现,不难发现,最终他们都是调用了同一个方法,如下:

// RCTCxxBridge.mm

- (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:方法
    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 (self->_reactInstance) {
        auto registry = RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
        self->_reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr),
                                            sourceUrlStr.UTF8String, !async);
      }
    } else if (self->_reactInstance) {
      self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
                                                 sourceUrlStr.UTF8String, !async);
    } else {
      std::string methodName = async ? "loadApplicationScript" : "loadApplicationScriptSync";
      throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
    }
  }];
}

如我在以上源码中加的注释所述,这个方法做了2件事:

1.发送一个将要执行JS的通知 名为RCTJavaScriptWillStartExecutingNotification

2.根据bundle的类型(是否为RAMBundle)分别调用_reactInstance的不同方法。如果是RAMBundle则调用_reactInstance的loadRAMBundle:方法,否则调用_reactInstance的loadScriptFromString:方法。因篇幅问题,因篇幅问题,此处不对RAM bundle展开介绍。

// Instance.cpp

// load普通的 JS bundle
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                    std::string sourceURL,
                                    bool loadSynchronously) {
  if (loadSynchronously) {
    loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
  } else {
    loadApplication(nullptr, std::move(string), std::move(sourceURL));
  }
}

// load RAM bundle
void Instance::loadRAMBundle(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                             std::unique_ptr<const JSBigString> startupScript,
                             std::string startupScriptSourceURL,
                             bool loadSynchronously) {
  if (loadSynchronously) {
    loadApplicationSync(std::move(bundleRegistry), std::move(startupScript),
                        std::move(startupScriptSourceURL));
  } else {
    loadApplication(std::move(bundleRegistry), std::move(startupScript),
                    std::move(startupScriptSourceURL));
  }
}

如上,我们发现无论是加载RAM bundle还是加载普通的bundle都调用了Instance的loadApplicationSync或loadApplication方法。下面我们来看这两个方法:

// Instance.cpp 

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

void Instance::loadApplicationSync(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                                   std::unique_ptr<const JSBigString> string,
                                   std::string sourceURL) {
  nativeToJsBridge_->loadApplicationSync(std::move(bundleRegistry), std::move(string),
                                         std::move(sourceURL));
}

如上,我们发现,Instance执行JS的方法又调用到了nativeToJsBridge这一层:

// NativeToJsBridge.cpp 

void NativeToJsBridge::loadApplication(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {

  runOnExecutorQueue(
      [this,
       bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
       startupScript=folly::makeMoveWrapper(std::move(startupScript)),
       startupScriptSourceURL=std::move(startupScriptSourceURL)]
        (JSExecutor* executor) mutable {
    auto bundleRegistry = bundleRegistryWrap.move();
    // 如果是RAM bundle则把 bundle 传给 executor
    if (bundleRegistry) {
      executor->setBundleRegistry(std::move(bundleRegistry));
    }
    // 调用JSIExecutor加载脚本
    try {
      executor->loadApplicationScript(std::move(*startupScript),
                                      std::move(startupScriptSourceURL));
    } catch (...) {
      m_applicationScriptHasFailure = true;
      throw;
    }
  });
}

void NativeToJsBridge::loadApplicationSync(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {
  if (bundleRegistry) {
    m_executor->setBundleRegistry(std::move(bundleRegistry));
  }
  try {
    m_executor->loadApplicationScript(std::move(startupScript),
                                          std::move(startupScriptSourceURL));
  } catch (...) {
    m_applicationScriptHasFailure = true;
    throw;
  }
}

如上,loadApplication和loadApplicationSync这两个方法实现基本一致,都是调用了成员变量m_executor的loadApplicationScript方法,区别在于loadApplication把代码放到了m_executorMessageQueueThread中去执行,而loadApplicationSync在当前线程执行。让我们来看下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_->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();
}

如上,loadApplicationScript方法主要做了3件事:

1.将Native侧的NativeModuleProxy对象注入到global的nativeModuleProxy上,相当于global.nativeModuleProxy = new NativeModuleProxy。

2.然后向global中注入了nativeFlushQueueImmediate,nativeCallSyncHook 2个方法。相当于global.nativeFlushQueueImmediate = JSIExecutor::callNativeModules(args); global.nativeCallSyncHook = JSIExecutor::nativeCallSyncHook(args);注入成功后,JS侧的global调用nativeFlushQueueImmediate或nativeCallSyncHook这两个方法就会直接调用到JSIExecutor对应的方法上。

3.调用runtime_->evaluateJavaScript方法,最终调用到JavaScriptCore的JSEvaluateScript函数。对JavaScriptCore了解的开发者应该都知道JSEvaluateScript的作用就是在JS环境中执行JS代码。

4.JS脚本执行完成,执行flush操作。flush函数的主要作用就是执行JS侧的队列中缓存的对native的方法调用。

evaluateJavaScript

上面说runtime最终调用到了JavaScriptCore的JSEvaluateScript函数。让我们再来看下JSCRuntime的evaluateJavaScript实现:

// JSCRumtime.cpp

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;
  JSValueRef res =
      JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);

  return createValue(res);
}

不难看出,最终还是调用的JavaScriptCore的JSEvaluateScript这个函数来执行JS代码。对JavaScriptCore或Hybrid开发有了解的人应该对这个函数非常熟悉,这个函数最关键的是前两个参数ctx和script,分别代表JS执行环境和将要执行的脚本字符串。

flush

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

// JSIExecutor.cpp

void JSIExecutor::flush() {
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_), true);
    return;
  }
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
  if (!batchedBridge.isUndefined()) {
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    callNativeModules(nullptr, true);
  }
}

上面的逻辑是:

1.如果JSIExecutor的flushedQueue_函数不为空,则通过函数flushedQueue_获取待调用的方法queue,然后执行callNativeModules。(第一次调用flush()时,flushedQueue_必为空,稍后在bindBridge()中才bind flushedQueue_)

2.以"__fbBatchedBridge"作为属性key去global中取对应的值也就是batchedBridge,batchedBridge本质上是JS侧的MessageQueue类实例化的一个对象

3.如果获取到了JS侧定义的batchedBridge对象,则执行bindBridge操作(我们知道在JS初始化环境的时候,JS的batchedBridge这个值已经被初始化为MessageQueue对象,在BatchedBridge.js中,创建了一个名为BatchedBridge的MessageQueue对象,并设置到global的__fbBatchedBridge属性里),即把batchedBridge中的方法和Native侧JSIExecutor的方法进行绑定。这些bind操作本质上是native指针指向JS函数。例如:把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor对象的callFunctionReturnFlushedQueue_进行绑定;把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_进行绑定;把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_进行绑定。把batchedBridge中的callFunctionReturnResultAndFlushedQueue 和 JSIExecutor中的callFunctionReturnResultAndFlushedQueue_进行绑定。bind完成之后,执行callNativeModules方法。

4.如果没有获取到JS侧定义的batchedBridge对象,则直接执行callNativeModules方法,即没有bind操作。

让我们继续看bindBridge方法的实现吧:

// JSIExecutor.cpp

void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");

    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把global中的函数变量赋值给了JSIExecutor的成员变量指针,这为后面Native call JS做好了准备。

Native调用JS

上面说了bindBridge方法中把global.batchedBridge中的方法和Native侧JSIExecutor的方法进行绑定。本质上就是Native指针指向JS函数(例如:JSIExecutor::callFunctionReturnFlushedQueue_ = global.batchedBridge.callFunctionReturnFlushedQueue)。这样就可以在native侧直接调用到JS函数,实现native调用JS。本节将从源码的角度介绍Native调用JS的相关细节。

执行完JS源码完成后,在RCTCxxBridge中会发送一个名为RCTJavaScriptDidLoadNotification的通知。如下:

// RCTCxxBridge.mm

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
  // 可以在任何执行JS的线程调用
  dispatch_block_t completion = ^{
    // 在主线程上执行状态更新和通知,这样我们就不会遇到RCTRootView的时序问题
    dispatch_async(dispatch_get_main_queue(), ^{
      // 主线程发送一个通知
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];
    });
  };

  if (sync) {
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }
}

RCTRootView监听到这个通知后执行了javaScriptDidLoad:方法,然后沿着Instance->NativeToJsBridge->JSIExecutor这个调用链调用了JSIExecutor::callFunction方法,方法内调用了JSIExecutor的callFunctionReturnFlushedQueue_方法,bindBridge一节中介绍了callFunctionReturnFlushedQueue_是通过runtime将native指针指向JS函数。所以,就相当于调用JS MessageQueue的callFunctionReturnFlushedQueue方法,该方法接收调用JS方法所需的moduleId、methodId和参数,执行完毕后JS会给Native返回一个queue,该queue中是一系列JS需要native侧执行的方法。最后这个queue被交给callNativeModules进行调用。详细调用过程如下:

// RCTRootView.m

- (void)javaScriptDidLoad:(NSNotification *)notification
{
  RCTBridge *bridge = notification.userInfo[@"bridge"];
  if (bridge != _contentView.bridge) {
    [self bundleFinishedLoading:bridge];
  }
}

- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
                                                    bridge:bridge
                                                  reactTag:self.reactTag
                                            sizeFlexiblity:_sizeFlexibility];
  [self runApplication:bridge];
  [self insertSubview:_contentView atIndex:0];
}

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

// NativeToJsBridge.cpp
// 该方法可以在任何线程调用
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
  __weak __typeof(self) weakSelf = self;
  [self _runAfterLoad:^(){
    __strong __typeof(weakSelf) strongSelf = weakSelf;

    if (strongSelf->_reactInstance) {
      // 调用Instance的callJSFunction方法
      strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
                                             convertIdToFollyDynamic(args ?: @[]));

      // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
      // the block is invoked after callJSFunction
      if (completion) {
        if (strongSelf->_jsMessageThread) {
          strongSelf->_jsMessageThread->runOnQueue(completion);
        } else {
          RCTLogWarn(@"Can't invoke completion without messageThread");
        }
      }
    }
  }];
}

// Instance.cpp
void Instance::callJSFunction(std::string &&module, std::string &&method,
                              folly::dynamic &&params) {
  callback_->incrementPendingJSCalls();
  // 调用NativeToJSBridge的callFunction方法
  nativeToJsBridge_->callFunction(std::move(module), std::move(method),
                                  std::move(params));
}

// NativeToJsBridge.cpp
void NativeToJsBridge::callFunction(
    std::string&& module,
    std::string&& method,
    folly::dynamic&& arguments) {
  runOnExecutorQueue([this, module = std::move(module), method = std::move(method), arguments = std::move(arguments), systraceCookie]
    (JSExecutor* executor) {
      // 调用JSIExecutor的callFunction方法
      executor->callFunction(module, method, arguments);
    });
}

// JSIExecutor.cpp
void JSIExecutor::callFunction(
    const std::string &moduleId,
    const std::string &methodId,
    const folly::dynamic &arguments) {

  Value ret = Value::undefined();
  try {
    scopedTimeoutInvoker_(
        [&] {
          // 调用callFunctionReturnFlushedQueue_并把JS需要Native侧执行的方法queue作为返回值返回,赋值给ret
          // 传入JS moduleId、methodId、arguements
          // 返回值 queue
          ret = callFunctionReturnFlushedQueue_->call(
              *runtime_,
              moduleId,
              methodId,
              valueFromDynamic(*runtime_, arguments));
        },
        std::move(errorProducer));
  } catch (...) {
    std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
  }
  // native侧刷新queue中需要执行的方法
  callNativeModules(ret, true);
}

上面介绍了通过MessageQueue的callFunctionReturnFlushedQueue实现Native调用JS。除此之外还有其他3个与Native call JS相关的函数,我们已经在bindBridge中见过了。他们分别是:invokeCallbackAndReturnFlushedQueue、flushedQueue、callFunctionReturnResultAndFlushedQueue。看名字就知道他们的作用,这里不做详细介绍。 接下来JS的MessageQueue.js对callFunctionReturnFlushedQueue的实现:

// MessageQueue.js

callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
  this.__guard(() => {
    this.__callFunction(module, method, args);
  });
  return this.flushedQueue();
}
  
__callFunction(module: string, method: string, args: any[]): any {
  const moduleMethods = this.getCallableModule(module);
  const result = moduleMethods[method].apply(moduleMethods, args);
  return result;
}
  
flushedQueue() {
  this.__guard(() => {
    this.__callImmediates();
  });
  const queue = this._queue;
  this._queue = [[], [], [], this._callID];
  return queue[0].length ? queue : null;
}
 

可以看出callFunctionReturnFlushedQueue主要做了两件事:

1.通过moduleId和methodId完成方法的调用(__callFunction函数)

2.返回一个queue(flushedQueue函数)

至此,Native调用JS相关的实现基本介绍完了。让我们总结下:

JS代码执行JS上下文环境都已经初始化,MessagQueue相关代码也会被调用,然后会通过runtime让Native指针指向JS函数,后面Native call JS都是通过这4个函数完成的。JS代码执行完毕后,RCTCxxBridge会发送一个名为RCTJavaScriptDidLoadNotification的通知给RCTRootView。然后经过RCTRootView->RCTBridge->RCTCxxBridge->Instance->NativeToJsBridge->JSIExecutor层层调用,最终通过调用callFunctionReturnFlushedQueue完成Native对JS的调用。Native call JS的四个核心函数如下,至此Native call JS基本介绍完了。

flushedQueue
callFunctionReturnFlushedQueue
invokeCallbackAndReturnFlushedQueue
callFunctionReturnResultAndFlushedQueue

JS调用Native

前面说过,JS中global.__fbGenNativeModule属性其实就是NativeModules.js中定义的genModule函数。在加载JS脚本的时候,将JSINativeModule的m_genNativeModuleJS指向了global.__fbGenNativeModule。即global.__fbGenNativeModule == genModule == m_genNativeModuleJS。在执行JS源码时候,最终会调用到JSIExecutor::loadApplicationScript方法。这个方法中初始化了一个nativeModuleProxy对象并设置给了global.nativeModuleProxy。初始化nativeModuleProxy对象会触发nativeModuleProxy的get方法,get方法最终调用到JSINativeModules的createModule方法,createModule中调用了m_genNativeModuleJS方法即JS侧genModule函数,方法入参是native侧的ModuleConfig对象,返回值是moduleInfo(形如:{modulename, moduleInfo},moduleName是模块名,moduleInfo是这个模块信息,包括模块方法)。JS侧拿到这个返回值进行缓存,后续通过缓存的这个moduleInfo获取native侧的模块配置,进而调用native方法,例如NativeModule.moduleName.methodName。

需要说明的是,通常情况下,JS是不会“直接的”调用OC方法的。当我们在JS中通过NativeModule调用native方法时,模块ID和方法ID会被加入一个名为_queue的队列,等到native侧调用JS方法时,顺便把这个队列作为返回值返回给native侧。Native侧再一一解析队列中的每一个moduleID和methodID后,封装成NSInvocation完成调用。如下是JS调用Native的流程图:

JS call Native 流程解析

我们知道MessageQueue.js承接了Native和JS通信的任务。在Native调用JS一节中我们知道了callFunctionReturnFlushedQueue这个函数用于Native call JS,并把JS中的queue返回给Native。这个queue存储了一系列JS对Native的调用。native侧拿到这个queue后,就会解析这个queue中的内容,得到相关模块的配置和参数,并进行动态调用。iOS上的调用主要是把这些配置和参数封装NSInvocation实例,进行调用。其调用顺序大致如下:

// JSIExecutor.app
void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
  // delegate_是JsToNativeBridge类型的实例
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}

// JsToNativeBridge.cpp
void callNativeModules(
  __unused JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
  
  for (auto& call : parseMethodCalls(std::move(calls))) {
    m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
  }
}

// ModuleRegistry.cpp
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
  modules_[moduleId]->invoke(methodId, std::move(params), callId);
}

// RCTNativeModule.mm
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &&params, int callId) {
  __weak RCTBridge *weakBridge = m_bridge;
  __weak RCTModuleData *weakModuleData = m_moduleData;
  
  invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
}

// RCTNativeModule.mm
static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic &params) {
  id<RCTBridgeMethod> method = moduleData.methods[methodId];
  NSArray *objcParams = convertFollyDynamicToId(params);
  id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
  return convertIdToFollyDynamic(result);
}

// RCTModuleMethod.mm
- (id)invokeWithBridge:(RCTBridge *)bridge
                module:(id)module
             arguments:(NSArray *)arguments
{
 [_invocation invokeWithTarget:module]; 
}

以上,是JS call Native 的方法调用链,主要是JSIExecutor先收到JS侧的调用请求,然后将请求转发给JSIExecutor实例的m_delegate,也就是调用m_delegate的callNativeModules方法,m_delegate本质上就是一个JSToNativeBridge实例,他是在NativeToJSBridge的初始化列表中初始化JSIExecutor时传给JSIExecutor的。m_delegate继续调用m_registry的callNativeMethod方法,m_registry是m_delegate的一个成员变量,也就是JSToNativeBridge的成员变量。然后调用经由NativeModule和RCTBridgeMethod转发,最后在RCTBridgeMethod中通过NSInvocation的方式完成了native侧方法的调用。

至此,JS调用Native方法就介绍完了。

三个疑问

你可能有3个疑问:

1.为什么JS不同步调用native方法而选择异步?

2.为什么RN不主动调用JS而是把调用缓存到队列,而是等native call JS时再把队列以返回值的形式返回给native?这样JS还能跑的通吗?

3.设计成这样如何保证native侧的方法可以得到及时的调用?

对于第一个问题:我们知道JS代码是运行在JS线程而非main thread,并且JS是单线程,如果同步调用native方法就会block住JS代码的运行,所以RN选择了JS和Native异步通信。

对于第二个问题:JS不会主动传递数据给OC,在调OC方法时,会把ModuleID,MethodID等数据加到一个队列里,等OC过来调JS的任意方法时,再把这个队列返回给OC,此时OC再执行这个队列里要调用的方法。让我们回顾下iOS的事件传递和响应机制就会恍然大悟,在Native开发中,只在有事件触发的时候,才会调用native代码。这个事件可以是启动事件、触摸事件、滚动事件、timer事件、系统事件、回调事件。而在React Native里,本质上JSX Component最终都是native view。这些事件发生时OC都会调用JS侧相应的方法以保证JS侧和native侧保持同步,JS处理完这些事件后再执行JS想让OC执行的方法,而没有事件发生的时候,是不会执行任何代码的,这跟native开发里事件响应机制是一致的。在native调用JS的时候,JS把需要native处理的方法队列返回给native侧让native去处理。这样既保证了JS和native侧事件和逻辑的同步,JS也可以趁机搭车call native,避免了JS和native侧频繁的通信。 RN的这种设计还是很合理的,真的是巧夺天工,这种设计思路还是值得借鉴的。

对于第三个问题:JS只是被动的等待native call JS,然后趁机把队列返回给native,那么如何保证方法调用的及时性呢?换句话说,如果native迟迟不调用JS,那JS侧队列中一大堆方法旧只能干等着吗?答案当然不是这样的,JS规定了一个时间阈值,这阈值是5ms,如果超过5ms后依旧没有native call JS。那么JS就会主动触发队列的刷新,即立即让native侧执行队列中缓存的一系列的方法。JS侧触发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);
    }
  }

如上,每次JS想要call native,都会判断距上一次flush queue的时间间隔,如果间隔大于或等于5ms,就调用global.nativeFlushQueueImmediate方法,把queue作为入参传给native。

大结局

好了,虽然讲了很多,也很啰嗦,但事无巨细,尤其是对于RN这种庞大的工程框架,一篇文章是很难覆盖各个细节的。对于一些重要的细节,日后找机会写文章单独分析。最后总结一下RN的初始化启动流程:

RN的启动流程主要分4个阶段:bridge初始化阶段、源码加载阶段、源码执行阶段、运行JSApplication阶段

bridge初始化阶段

bridge初始化阶段主要是初始化并配置了RCTBridge实例、RCTCxxBridge实例、Instance实例、NativeToJSBridge实例、JSIExecutor实例。

  1. 在APPDelegate的启动方法中创建了RCTBridge和一个RCTRootView,然后在RCTBridge中创建了一个名为batchedBridge的RCTCxxBridge实例,并调用了self.batchedBridge start方法。在start方法里先创建了一个专门用来运行JS代码的JS线程,这个线程绑定到了一个runloop以免退出。然后将所有注册的module生成RCTModuleData,并根据需要调用他们的初始化方法,以上操作都是在main thread中进行的。
  2. 接下来一边在JS线程中执行Intance的初始化方法,一边异步进行JS源码的加载。Intance的初始化主要是在Instance::initializeBridge方法里创建了一个名为nativeToJsBridge_的NativeToJSBridge实例变量。然后NativeToJsBridge的初始化方法里通过executorFactory创建了一个名为m_executor的JSIExecutor实例并传入了一个runtime变量。至此,Instance、NativeToJSBridge、JSIExecutor初始化完成。

源码加载阶段

start方法中初始化Instance的同时还在load JS源码,JS源码加载分为同步和异步。JS bundle类型分为RAM bundle和普通bundle。load完成后返回JS sourceCode进入源码执行阶段。

源码执行阶段

当JS sourceCode加载完成且native module也初始化也完成后,就会继续走执行JS源码的逻辑,主要是初始化JS上下文环境,并建立Native call JS的能力,即Native指针指向JS函数。

  1. start方法先是调用executeSourceCode方法,然后经过RCTCxxBridge -> Instance -> NativeToJsBridge -> JSIExecutor层层调用最终会执行到JSIExecutor::loadApplicationScript方法。在JSIExecutor::loadApplicationScript中通过runtime向global中注入了nativeModuleProxy。并且将JSIExecutor::nativeFlushQueueImmediate、JSIExecutor::nativeCallSyncHook注入到global中。
  2. 向global注入nativeModuleProxy的时候,会触发nativeModuleProxy的get方法,目的是生成native module的配置信息并返回给JS。 主要逻辑是通过nativeModuleProxy -> JSINativeModules.getModule -> JSINativeModules.createModule层层调用,最后调用JS在global中注册的genModule这个方法把native module配置信息生成对应的JS对象,JS会缓存这个对象用于将来调用native。当然native侧早就有了一份module配置信息表,这个表存储在ModuleRegistry中。日后JS call Native 都是传递的模块ID和方法ID,然后native再根据ID去查表完成方法调用。
  3. 在JSIExecutor::loadApplicationScript中会调用JSCRumtime的evaluteJavaScript方法,进而调用到JavaScriptCore的JSEvaluateScript方法执行JS源码。
  4. 在JS源码执行完后,JSIExecutor::loadApplicationScript中会调用flush()方法刷新JS的messageQueue中缓存的方法队列。

运行JS Application阶段

上面JS脚本执行完后,RCTCxxBridge会发送一个名为RCTJavaScriptDidLoadNotification的通知给RCTRootView执行runApplication相关的逻辑。RCTRootView收到通知后会把运行JS所需的moduleName、methodName、appKey和页面所需的参数传给JS。moduleName通常是@"AppRegistry",用于调用RN的名字为"AppRegistry"的组件。methodName通常是@"runApplication",用于调用JS的AppRegistry.runApplication方法。appKey是页面的key,也是AppRegistry.runApplication的入参,用于唯一标识一个组件,本文中是@"NewProject"。参数很好理解了,就是初始化页面的一些业务参数。

  1. 执行runApplication相关的逻辑链条:RCTRootView -> RCTBridge->RCTCxxBridge->Instace->NativeToJsBridge->JSIExecutor。通过以上层层调用后,最终调用到JSIExecutor的callFunction方法,如果没有绑定JS和Native侧的方法则调用bindBridge执行bind逻辑,将MessageQueue.js中定义的4个方法绑定到JSIExecutor的成员变量上(相当于Native指针指向JS函数),这4个函数是flushQueue、callFunctionReturnFlushedQueue、invokeCallbackAndReturnFlushedQueue、callFunctionReturnResultAndFlushedQueue。关于bind这4个函数的逻辑在源码执行阶段已经讲过了。
  2. 然后调用callFunctionReturnFlushedQueue方法获取JS待native刷新的方法队列,把队列通过JSIExecutor::callNativeModules交给JSIExecutor的m_delegate进行处理。m_delegate是一个JSToNativeBridge的实例。专门负责处理JS call Native相关的逻辑。
  3. 第2步中调用callFunctionReturnFlushedQueue时会把@"AppRegistry"、@"runApplication"、appKey、arguments作为入参传递个JS,JS侧收到这个消息后,就调用了JS中的AppRegistry的runApplication方法。到这里,JS的入口就被调用,界面就会渲染出来。JS的入口如下:

总结下来,React Native用iOS自带的JavaScriptCore作为JS的解析引擎,即JS和Native的相互通信是经过JavaScriptCore机制来进行的。但并没有用到JavaScriptCore提供的一些可以让JS与OC互调的特性,而是自己实现了一套机制,这套机制可以通用于所有JS引擎上。

在程序启动阶段会收集所有native暴露给JS的模块和方法。然后初始化阶段会创建并配置JS相关的桥。在执行JS源码阶段,Native的JSIExecutor会注入nativeModuleProxy和两个方法到JS全局变量global中,相当于JS指针指向Native变量,这样就建立了JS调用Native的能力。注入到global中的两个方法是nativeFlushQueueImmediate和nativeCallSyncHook,将来JS侧调用这两个方法就会走到Native侧的方法实现中。

但是JS call Native并不直接调用Native的方法,而且先放入一个JS队列中,每次Native call JS时JS都会将这个队列返回给Native,Native拿到这个队列后根据队列中的moduleID、methodID和方法参数等信息生成NSInvocation,完成Native的方法调用。这种实现方式避免了JS和Native的频繁通信。

JS脚本加载并执行完成后native会调用JS,告诉JS可以runApplication了,接下页面救护被渲染出来。
至此,全篇完!

文/VV木公子(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载请联系作者获得授权,并注明出处。

如果您是iOS开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容