一篇文章详解React Native初始化和通信机制

作者: VV木公子 | 来源:发表于2020-03-22 04:09 被阅读0次

    致敬

    开始准备写这一篇文章的时候,中国的新型冠状病毒肺炎疫情还在继续,累积确诊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)线程里最主要创建了native->JS的桥,即nativeToJsBridge_。然后instance持有了这个nativeToJsBridge_以及其他2个外部传进来的参数 callback、moduleRegistry。至此Instance的初始化就结束了。下面我们来看一下这四个参数:

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

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

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

    第四个参数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开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!

    相关文章

      网友评论

        本文标题:一篇文章详解React Native初始化和通信机制

        本文链接:https://www.haomeiwen.com/subject/lwkdyhtx.html