1. 程式人生 > Android開發 >ReactNative iOS 原始碼解析

ReactNative iOS 原始碼解析

前言

雖然在跨平臺這塊谷歌搞了 Flutter 出來,但是從目前的生態和大廠應用上來講,ReactNative 優勢更明顯些。雖然這是一個 15 年就推出的跨平臺框架,但是這幾年 ReactNative 團隊也一直在對它進行優化,尤其在大家平時詬病的 Bridge 那塊,做了很大的調整,程式碼也基本上從開始的 OC 形式直接改成了 cpp 的橋接,基本上達到了和 JavaScript 的無縫銜接,另外一點這樣也可以做到安卓 iOS 雙端程式碼統一。個人覺得學習它的設計思想和設計模式要遠遠大於它未來的生態,所以基於 0.61.0 版本,總結了一下 ReactNative 的原始碼,本篇作為開篇先從 ReactNative 的業務程式碼開始,一步步縱深探究下其內部原理。

開篇先甩張圖,看一下它的大體結構,ReactNative 架構分層還是比較明顯的,最上層業務層,是我們日常編寫的業務元件,其中包括用 Native 編寫的和用 JS 編寫。中間是框架層,Bridge 部分除了 RCTBridge 和 RCTCxxBridge 之外,其餘都是由 cpp 構建。最下面是 iOS 的系統庫,大名鼎鼎的 JavaScriptCore,cpp 和 JSCore 進行無縫銜接,讓前端的小夥伴可以直接上手 Native 開發,具體細節後面都會說,這裡只是先簡單的瞭解一下 ReactNative 的架構組成。

從ReactNative的初始化開始

根據 ReactNative 官方檔案描述,當我們想要一個 ViewController 成為 RN 的容器的話,具體實現應該是這樣的:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
    // 初始化rootView
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                    moduleName: @"RNHighScores"
initialProperties:nil launchOptions: nil]; self.view = rootView; } 複製程式碼
  1. 根據本地 JSBundle 的 url 初始化一個 RootView。
  2. 再把這個 rootView 賦值給 VC 的 View。

在 RCTRootView 初始化方法裡面還會建立一個 RCTBridge,這個 RCTBridge 就是 ReactNative 框架層中的 Bridge 部分,也是極其重要的部分

// RCTRootView.m

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions
{
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
                                             launchOptions:launchOptions];

  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
複製程式碼
// RCTBridge.m

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

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

- (Class)bridgeClass
{
  return [RCTCxxBridge class];
}
複製程式碼

RCTRootView.m 內部最後會走到 RCTCxxBridge 的 -start 方法。

// RCTCxxBridge.mm

- (void)start
{
  // 1.建立一個常駐的執行緒
  _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                      selector:@selector(runRunLoop)
                                        object:nil];

  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();
  // 2.載入原生模組
  [self registerExtraModules];
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  [self registerExtraLazyModules];
  // 3.建立Instance例項
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;
  // 4.JSExecutorFactory工廠,生產環境它的真實型別是JSIExecutor
  // Prepare executor factory (shared_ptr for copy into block)
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {
    if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
      id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
      executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
    }
    if (!executorFactory) {
      executorFactory = std::make_shared<JSCExecutorFactory>(nullptr);
    }
  } else {
    id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
    executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor,^(NSError *error) {
      if (error) {
        [weakSelf handleError:error];
      }
    }));
  }

  // 5.初始化底層Bridge
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

  // 6.載入JS
  dispatch_group_enter(prepareBridge);
  __block NSData *sourceCode;
  [self loadSource:^(NSError *error,RCTSource *source) {
    if (error) {
      [weakSelf handleError:error];
    }

    sourceCode = source.data;
    dispatch_group_leave(prepareBridge);
  } onProgress:^(RCTLoadingProgress *progressData) {
  ...
  }];

  // 7.執行JS
  dispatch_group_notify(prepareBridge,dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0),^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways,@"");
}
複製程式碼

-start 方法貫穿了整個 ReactNative 的啟動流程,整個 -start 方法主要做了以下事情:

  1. 建立了個 JS 執行緒 _jsThread,並在內部開了個 runloop 讓它長駐,初始化底層 Bridge、執行 JS 程式碼都是在這個執行緒裡執行。
  2. 載入原生模組,將所有暴漏給JS使用的原生 RCTBridgeModule 初始化,每個原生模組都被初始化為 RCTModuleData,並將其仍到陣列裡。
  3. 初始化 Instance 例項,Instance 是公共層 Bridge 的最上層,資料公共部分的入口,到這裡已經進入了 cpp 檔案。Instance 是對底層 Bridge 的一層包裝,提供了一些執行Call JS 的API。
  4. JSExecutorFactory 使用工廠模式,在不同場景構建出不同的 JSExecutor,其中生產環境使用 JSCExecutorFactory,用於構建 JSIExecutor,這個東西是底層 Bridge 的一箇中間環節,先按住不講後面細說。
  5. 在 JS 執行緒初始化底層 Bridge。
  6. 載入 JS 程式碼。
  7. 執行 JS 程式碼。

上面原始碼用到了一個 dispatch_group_t 型別的 prepareBridgedispatch_group_tdispatch_group_notify 聯合使用保證非同步程式碼同步按順序執行,從上面的啟動流程來看,原生模組的載入在主執行緒,底層 Bridge的初始化是在 JS 執行緒,JS 程式碼的載入可以同步也可以非同步,這些工作都是非同步執行的,dispatch_group_notify 能夠保證這些工作都執行完畢,在執行 JS 程式碼。關於 Bridge 的構建和 JS 程式碼的載入執行,流程都較長下面會細說。

原生模組載入

先用一個官方檔案的例子,看看原生的模組都是如何被 ReactNative 進行載入並提供給 JS 側使用的,在 ReactNative 中建立一個原生模組,以檔案日曆模組為例:

// CalendarManager.h

#import <React/RCTBridgeModule.h>
@interface CalendarManager : NSObject <RCTBridgeModule>
@end
複製程式碼
// CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTLog.h>

@implementation CalendarManager

RCT_EXPORT_MODULE();

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

@end
複製程式碼
  1. 建立一個 NSObject 的子類,叫 CalendarManager,並且遵循 RCTBridgeModule 協議。
  2. 在原始檔內實現 RCT_EXPORT_MODULE()
  3. 使用 RCT_EXPORT_METHOD 巨集,實現需要給 JS 匯出的方法。

這樣一個原生模組就建立完了,可以讓 JS 側直接呼叫:

// index.js

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

這樣一個簡單的呼叫就結束了,我們只知道這樣呼叫就能把引數傳遞到 CalendarManager裡面來,然而並不知道 ReactNative背後做了什麼,知其然不知其所以然 那我們深入看下原始碼,先看看 RCT_EXPORT_MODULE() 做了什麼。

這是一個巨集的巢狀函式,層層展開如下:

// RCTBridgeModule.h

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

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

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

  // Register module
  dispatch_barrier_async(RCTModuleClassesSyncQueue,^{
    [RCTModuleClasses addObject:moduleClass];
  });
}
複製程式碼

展開巨集自動幫我們實現了三個函式,@# 的意思是自動把巨集的引數 js_name 轉成字元通過 +moduleName 返回原生模組名稱,重寫 + (void)load 函式,呼叫 RCTRegisterModule() 把類註冊到原生模組類集合。App在啟動後,實際上所有模組都走了 +load 方法,也就是說都呼叫了 RCTRegisterModule() 方法進行註冊。RCTRegisterModule() 裡面首先判斷註冊的模組是否遵循了 RCTBridgeModule 協議,如果遵循了則會放入到 RCTModuleClasses 陣列中,這裡處於陣列執行緒安全考慮,使用柵欄函式加了讀寫鎖,其實還有個讀函式後面會說。

第一個巨集說完了再來看看第二個巨集,RCT_EXPORT_METHOD 都做了些啥。RCT_EXPORT_METHOD 也是個展開巨集,但是比上面的要複雜些,展開如下:

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

看展開結果,RCT_EXPORT_METHOD 巨集首先幫我們補全了 - (void),使得 - (void) 加上引數還原了我們上面給 JS 定義的方法,還聲成了一個以 __rct_export__ 拼接上 __LINE____COUNTER__ 為方法名的函式,這是兩個C語言巨集,分別代表著行號與一個內建計數器,大概意思是要生成一個唯一的標識。該函式返回了一個 RCTMethodInfo 型別的物件, 包含了匯出函式資訊,有 JS 名、原生函式名、是否為同步函式。

這些方法定義好後,是如何被 JS 所載入的,那我們繼續深入原始碼,探究一下 CalendarManager 原生模組,是如何被 ReactNative 載入並被 JS 呼叫到的。

回到 -start 方法的第三步,載入原生模組:

// RCTCxxBridge.mm

- (void)start
{
  dispatch_group_t prepareBridge = dispatch_group_create();
  // 載入手動匯出的原生模組
  [self registerExtraModules];
  // 載入自動匯出的原生模組
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  // 載入除錯模式所需原生模組
  [self registerExtraLazyModules];
  ...
}
複製程式碼

我們以自動註冊的模組為例,探究下具體實現部分:

// RCTCxxBridge.mm

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

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

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

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

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways,@"");

  return moduleDataByID;
}
複製程式碼

這裡面主要建立了三個表:

  1. _moduleDataByID陣列:通過 RCTGetModuleClasses() 將前面註冊到陣列中的模組取出來,迴圈遍歷建立RCTModuleData型別物件,儲存到_moduleDataByID陣列中。實際上RCTModuleData把原生模組包裝了一層,原生模組的例項只是RCTModuleData中的一個屬性。這裡不僅例項化了原生模組,還做了一些其他事情,這個後面會說
  2. _moduleDataByName字典:key就是前面提到的,模組匯出巨集自動新增的 +moduleName 返回值,如果沒有設定,預設為當前類名。
  3. _moduleClassesByID陣列:將所有模組的Class裝進陣列。

到這裡所有原生模組就都被 ReactNative 載入並初始化完畢。這些原生模組都是JS側通過Bridge來進行呼叫,JSBridge 應該都是耳熟能詳的東西了,專門用來做 JS 和原生進行互動使用的工具,不管 Cordova框架 也好還是我前面幾篇文章說的 WKJavaScriptBridge 也好,都是使用的這種橋接技術,萬變不離其宗,ReactNative 也是通過 JSBridge 來做互動的,只不過是個 cpp 版的 bridge。這個 bridge 的構建還得從上面的 -start 說起。-start 函式在第五步裡面初始化了底層Bridge,我們在深入探究下底層 Bridge 的初始化流程。

底層Bridge構建

-start 中關於底層 bridge 構建的相關程式碼如下:

// RCTCxxBridge.mm

- (void)start
{
  // 建立JSThread
  _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                      selector:@selector(runRunLoop)
                                        object:nil];
  _jsThread.name = RCTJSThreadName;
  _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
  _jsThread.stackSize *= 2;
#endif
  [_jsThread start];

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

  __weak RCTCxxBridge *weakSelf = self;

  ...
  
  dispatch_group_enter(prepareBridge);
  // 在_jsThread中初始化底層bridge
  [self ensureOnJavaScriptThread:^{
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];
  ...
}
複製程式碼
  1. -start 的時候,建立了一個 JS 執行緒 _jsThread,這裡涉及到執行緒保活的知識,子執行緒預設不開啟 runloop,子執行緒在執行完任務後會被釋放掉,所以這裡在子執行緒開啟了個 runloop,讓執行緒一直存活,底層 Bridge 初始化,js bundle 包的載入/執行都在 JS 執行緒執行。
  2. _jsThread 執行緒中構建底層bridge。

繼續看 _initializeBridge: 都做了什麼:

// RCTCxxBridge.mm

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

- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
  _reactInstance->initializeBridge(
                                   std::make_unique<RCTInstanceCallback>(self),executorFactory,_jsMessageThread,[self _buildModuleRegistryUnlocked]);
}
複製程式碼
  1. 建立一個名為 _jsMessageThread 執行緒,綁定了 _jsThread 的 runloop,使其任務執行完畢不退出,被 RCTCxxBridge 持有,傳遞給底層 bridge。
  2. 初始化底層bridge,這是一個 C++ 函式,函式內部初始化了一個叫 NativeToJsBridge 的例項。
// Instance.cpp

void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback,std::shared_ptr<JSExecutorFactory> jsef,std::shared_ptr<MessageQueueThread> jsQueue,std::shared_ptr<ModuleRegistry> moduleRegistry) {
  callback_ = std::move(callback);
  moduleRegistry_ = std::move(moduleRegistry);
  jsQueue->runOnQueueSync([this,&jsef,jsQueue]() mutable {
    nativeToJsBridge_ = std::make_unique<NativeToJsBridge>(
        jsef.get(),moduleRegistry_,jsQueue,callback_);

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

  CHECK(nativeToJsBridge_);
}
複製程式碼

根據程式碼不難看出,最主要的工作是在上面傳進來的執行緒中,同步執行 NativeToJsBridge 的建立,nativeToJsBridge_ 的建立傳遞了三個引數:

  1. InstanceCallback:底層呼叫結束後給上層的回撥。

  2. JSExecutorFactory:生產環境實際為 JSIExecutor,後面這個類會細說。

  3. MessageQueueThread:上層傳遞過來的 _jsMessageThread

  4. ModuleRegistry:它負責兩個非常核心的任務:一是生成中間版本的原生模組配置資訊,進一步加工就可以最終匯入給 JS 端,二是作為JS call Native的中轉站。

ModuleRegistry

上面說了 moduleRegistry 中包括了所有native module資訊,具體還要回到 RCTCxxBridge.mm 看程式碼:

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

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

std::vector<std::unique_ptr<NativeModule>> createNativeModules(NSArray<RCTModuleData *> *modules,RCTBridge *bridge,const std::shared_ptr<Instance> &instance)
{
  std::vector<std::unique_ptr<NativeModule>> nativeModules;
  for (RCTModuleData *moduleData in modules) {
    if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
      nativeModules.emplace_back(std::make_unique<CxxNativeModule>(
        instance,[moduleData.name UTF8String],[moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },std::make_shared<DispatchMessageQueueThread>(moduleData)));
    } else {
      nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge,moduleData));
    }
  }
  return nativeModules;
}
複製程式碼

_buildModuleRegistryUnlocked 主要負責構建並返回一個 ModuleRegistry 例項,_moduleDataByID 正是前面初始化原生模組時候的 RCTModuleData 陣列,這裡遍歷陣列,將 RCTModuleData 模組生成對應的 cpp 版本的 NativeModule,在 ModuleRegistry 的建構函式裡,這些 NativeModule 又會被 ModuleRegistry 所持有:

ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules,ModuleNotFoundCallback callback)
    : modules_{std::move(modules)},moduleNotFoundCallback_{callback} {}
複製程式碼

這個 modules_ 至關重要,將來 JS 側想要獲取 Native 側提供的各種配置資訊都要靠它來獲取。既然 ModuleRegistry 裡面裝的都是 NativeModule,那再看看 NativeModule 職能。NativeModule 定義了一套介面用於獲取原生模組資訊、呼叫原生模組函式:

// NativeModule.h
class NativeModule {
 public:
  virtual ~NativeModule() {}
  //獲取模組資訊
  virtual std::string getName() = 0;
  virtual std::vector<MethodDescriptor> getMethods() = 0;
  virtual folly::dynamic getConstants() = 0;
  //呼叫原生函式
  virtual void invoke(unsigned int reactMethodId,folly::dynamic&& params,int callId) = 0;
  virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId,folly::dynamic&& args) = 0;
};
複製程式碼

NativeModule 只提供了介面並沒有實現,我把它理解成為一個介面,真正的實現是在 RCTNativeModule,實際上就是個 C++ 版本的原生模組描述類,它是對 OC 版本 RCTModuleData 的封裝,是面向底層 cpp 層 bridge 的。

獲取模組資訊,我們以 getConstants 為例,看名字也能看出來,這是在獲取模組的常量資訊。我們刨根問題往下走看看它是怎麼拿到常量資訊的:

// RCTNativeModule.mm

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

m_moduleData 就是我們 OC 版本的原生模組資訊 RCTModuleData,我們繼續往下走:

// RCTModuleData.mm

- (NSDictionary<NSString *,id> *)exportedConstants
{
  [self gatherConstants];
  NSDictionary<NSString *,id> *constants = _constantsToExport;
  _constantsToExport = nil; // Not needed anymore
  return constants;
}
複製程式碼
- (void)gatherConstants
{
  //如果有常量匯出並且是第一次匯出
  if (_hasConstantsToExport && !_constantsToExport) {
    RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@",_moduleClass]),nil);
    //確保原生模組存在
    (void)[self instance];
    //因為整個bridge的構建和js與native的通訊都是在jsthread中進行,所以如果需要在主執行緒獲取constantsToExport需要切換回主執行緒
    if (_requiresMainQueueSetup) {
      if (!RCTIsMainQueue()) {
        RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks",_moduleClass);
      }

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

經過上面的步驟我們不難發現,我們的原生module被 RCTModuleData 包了一層,RCTModuleData 又被 RCTNativeModule 包了一層,RCTNativeModule 又都裝到了 ModuleRegistry 裡,實際上 ModuleRegistry 也同樣被更底層的 JSINativeModules 持有著,而 JSINativeModules 又被更更底層的 JSIExcutor 持有著,CalendarManager <- RCTModuleData <- RCTNativeModule <- ModuleRegistry <- JSINativeModules <- JSIExcutor, JSIExcutor 會在執行 JS 程式碼的時候向 JS 側注入一個叫 NativeModuleProxy 的物件,JS 側呼叫到了這個物件,就會呼叫到Native側的 JSIExcutor 內,這樣我們原生模組的資訊是如何提供給 JS 側使用的路線就出來了。後面還會詳細的說 JSIExcutor NativeModuleProxy JSINativeModules。對 ReactNative原始碼沒有過瞭解的同學可能有點迷糊,不過不用擔心,後面說到 NativeModuleProxy 的時候,我會再用函式呼叫棧的形式再來一遍。

至此我們所有原生的模組資訊都已經準備完畢,目前來看所有的原生模組都註冊到了 ModuleRegistry 中,那不妨猜想以下,是不是隻要 JS 呼叫了 ModuleRegistry 就可以執行原生模組的方法了?事實確實如此,要想理解這個呼叫過程,需要先了解一下 NativeToJsBridgeNativeToJsBridge 是一 個C++ 類,前面說到的 Instance 是對 NativeToJsBridge 的一層包裝,NativeToJsBridgeInstance 更接近底層。

NativeToJsBridge

NativeToJsBridge 顧名思義是 Native Call JS 的集大成者,那麼它和JS Call Native有什麼關係呢,實際上它內部還持有著 JSExecutorJsToNativeBridge,並將 JsToNativeBridge 最終傳遞給 JSIExecutor,由 JSIExecutor 來觸發JS Call Native。實際 Native Call JS 底層也是 JSIExecutor 實現,這個 JSIExecutor 具體是做什麼的一直沒說,這個先按住不說,後面再說,先看看 NativeToJsBridge 的定義:

// NativeToJsBridge.cpp

class NativeToJsBridge {
public:
  friend class JsToNativeBridge;

  // 必須在主執行緒呼叫
  NativeToJsBridge(
      JSExecutorFactory* jsExecutorFactory,std::shared_ptr<ModuleRegistry> registry,std::shared_ptr<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::string sourceURL);

private:
  std::shared_ptr<JsToNativeBridge> m_delegate;
  std::unique_ptr<JSExecutor> m_executor;
  std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
};
複製程式碼

因為在模組載入那裡舉的例子並沒有包含Native給JS的回撥,實際上從上面的程式碼能看出來,如果有回撥的話會觸發 NativeToJsBridge 的 invokeCallback 方法,把 callbackId 回撥給JS側,JS 側拿到 callbackId 就可以執行相應的回撥了。先簡單介紹下它的成員變數和方法:

成員變數

  1. m_delegate:JsToNativeBridge型別的引用,主要用於JS call Native
  2. m_executor:這個是在前面說的工廠創建出來的類,生產環境是JSIExecutor,除錯環境是RCTObjcExecutor,這是底層通訊的集大成者,不管是JS Call Native還是Native Call JS都是基於它來進行。
  3. m_executorMessageQueueThread:並非NativeToJsBridge自己初始化,而是作為初始化引數傳遞進來的,是最外層的_jsMessageThread。這個執行緒具體做什麼用後面說。

主要方法

  1. void callFunction(std::string&& module,folly::dynamic&& args); 這個函式的意義就是通過module ID和method ID以及引數去呼叫JS方法

  2. void invokeCallback(double callbackId,folly::dynamic&& args); 這個函式的意義就是通過callbackId和引數觸發一個JS的回撥。通常是JS call Native method之後,native把一些非同步的執行結果再以callback的形式回撥給JS。

  3. void loadApplication( std::unique_ptr bundleRegistry,std::unique_ptr startupCode,std::string sourceURL); 這個方法的作用是執行JS程式碼,他還有一個兄弟叫做 loadApplicationSync,顧名思義,他兄弟是一個同步函式,所以他自己就是非同步執行JS程式碼。

建構函式

// NativeToJsBridge.cpp

NativeToJsBridge::NativeToJsBridge(
    JSExecutorFactory *jsExecutorFactory,std::shared_ptr<InstanceCallback> callback)
    : m_destroyed(std::make_shared<bool>(false)),m_delegate(std::make_shared<JsToNativeBridge>(registry,callback)),m_executor(jsExecutorFactory->createJSExecutor(m_delegate,jsQueue)),m_executorMessageQueueThread(std::move(jsQueue)),m_inspectable(m_executor->isInspectable()) {}
複製程式碼

NativeToJsBridge 內部把原生資訊 registry 和外部傳入的 callback 作為入參生成了 JsToNativeBridge 並持有, 這也是 JsToNativeBridge 能夠 Call Native 的原因,jsExecutorFactory 又通過JsToNativeBridge 和外部傳入的js執行緒生成了一個 executor 並持有,在生產環境下,這個executor就是 JSIExecutor。JS Call Native 實際上就是 JSIExecutor 來呼叫 JsToNativeBridge 實現,JSIExecutor 上面也有提到一直按住沒表,那我們再來看下JSIExecutor。

JSIExecutor

通過上面的流程,我們明白了 JSIExecutor 相比 NativeToJsBridge 顯得更底層,我們可以理解為 InstanceNativeToJsBridge 的包裝,NativeToJsBridgeJSIExecutor 的包裝,實際上在 Instance 中呼叫 callJSFunction:,它的呼叫順序應該是這樣的:Instance->NativeToJsBridge->JSIExecutorJSIExecutor 會呼叫更底層,這個後面說。同樣JS想要呼叫Native,也是一樣:JSIExecutor->JsToNativeBridge->ModuleRegistry。之前說了,在 NativeToJsBridge 的建構函式中 jsExecutorFactory 使用 JsToNativeBridge 例項 m_delegate 和 jsQueue 建立了 m_executor。這裡我們主要以生產環境的JSIExecutor為例介紹。除錯模式下請參考RCTObjcExecutor,他們都繼承自 JSExecutor。下面是兩種環境下 executor 的建立方式,生產環境的 JSIExecutor 通過 JSCExecutorFactory 生產,如下:

// JSCExecutorFactory.mm

 return std::make_unique<JSIExecutor>(
      facebook::jsc::makeJSCRuntime(),delegate,JSIExecutor::defaultTimeoutInvoker,std::move(installBindings));
複製程式碼

瞭解了它的由來後我們再簡要分析下 JSIExecutor 裡面的幾個關鍵屬性:

// JSIExecutor.h

class JSIExecutor : public JSExecutor {
  ...省略很多程式碼
  std::shared_ptr<jsi::Runtime> runtime_;
  std::shared_ptr<ExecutorDelegate> delegate_;
  JSINativeModules nativeModules_;
  ...
};
複製程式碼
  1. jsi::Runtime runtime_:實際上就是JSCRuntime,內部實現了Runtime的介面,提供了可以執行JS的能力,內部比較複雜,這是基於C語言版的JavaScriptCore實現,用於建立JS上下文,執行JS,像JS注入原生物件等功能。
  2. delegate_:這個delegate是在NativeToJsBridge中初始化JSIExecutor是傳遞進來的引數,這個引數正是JsToNativeBridge物件,負責JS Call Native。
  3. nativeModules_:由外部傳入的 ModuleRegistry 構造而成,在JS Call Native的時候,會通過 JSINativeModules 裡面的 ModuleRegistry 來獲取原生模組資訊,並把這個資訊通過 __fbGenNativeModule 函式傳遞給 JS 側,由此可見,原生模組資訊並不是主動匯入到 JS 側的,而是 JS 側到原生獲取的,大致流程如下,首先從 Native 側的 getModule 開始:
// JSINativeModules.cpp

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

複製程式碼

getModule 內部呼叫了 createModule,那麼 createModule 方法做了什麼呢:

// JSINativeModules.cpp

folly::Optional<Object> JSINativeModules::createModule(
    Runtime& rt,const std::string& name) {
  ...
  if (!m_genNativeModuleJS) {
  // 獲取JS側全域性函式__fbGenNativeModule
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt,"__fbGenNativeModule");
  }
 // 根據模組名,去原生模組註冊物件中取出模組資訊
  auto result = m_moduleRegistry->getConfig(name);
  ...
  // 呼叫__fbGenNativeModule並把原生模組資訊傳遞過去
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,valueFromDynamic(rt,result->config),static_cast<double>(result->index));
  ...

  return module;
}
複製程式碼
  1. 獲取JS側全域性函式 __fbGenNativeModule,用以呼叫這個函式可以呼叫到 JS 側,這個函式的具體細節,後面會講。
  2. 根據模組名,去 ModuleRegistry 中取出模組資訊,ModuleRegistry 的定義上面講過了,所有原聲模組的資訊都存在這裡。
  3. 呼叫 __fbGenNativeModule 這個全域性函式並把從 ModuleRegistry 取出來的原生模組資訊傳遞過去。

那麼 getModule 是哪裡呼叫的呢?

NativeModuleProxy

NativeModuleProxy 是 getModule 的唯一入口:

// JSIExecutor.cpp

class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  // 建構函式 JSIExecutor例項作為NativeModuleProxy建構函式的入參
  NativeModuleProxy(JSIExecutor &executor) : executor_(executor) {}
  
  // NativeModuleProxy 的 get方法 用於獲取native module資訊
  Value get(Runtime &rt,const PropNameID &name) override {
    return executor_.nativeModules_.getModule(rt,name);
  }
};
複製程式碼

那麼 NativeModuleProxy 這個 cpp 類又是在哪裡使用的呢?全域性搜尋 NativeModuleProxy,你會發現只有一個地方再使用 NativeModuleProxy,就是 JSIExecutor 的 loadApplicationScript 方法,原始碼如下:

// JSIExecutor.cpp 

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

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

  // ...
  
}
複製程式碼

runtime 是一個 JSCRuntime 型別物件,通過呼叫 rumtime_->global() 獲得一個全域性的 global 物件。然後又通過 setProperty 方法給 global 物件設定了一個名為 nativeModuleProxy 的物件。JS 側的 global 物件通過 "nativeModuleProxy" 這個名字即可訪問到 native 側的 NativeModuleProxy。本質上,JS側的 global.nativeModuleProxy 就是native側的 nativeModuleProxy。換句話說,我們在 JS 側的 NativeModules 對應的就是 native 側的 nativeModuleProxy。我們不妨再深入看以下 JS 側的原始碼:

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

我們在寫 ReactNative 程式碼時使用的 NativeModules 正是原生端的 nativeModuleProxy 物件,文章開頭的例子 NativeModules.CalendarManager,實際上都會先到 Native 側的 nativeModuleProxy,再到 JSINativeModules,再呼叫到 getModule 方法,通過 CalendarManager 模組名來獲取 CalendarManager 的資訊,然後 Call 全域性的 JS 函式 __fbGenNativeModule,把原生資訊傳遞給 JS 側。

原生模組資訊匯出

下面我們再以函式呼叫棧的形式,走一遍 CalendarManager 匯出常量的流程,也可以理解為 JS 側獲取 Native 側資訊的流程,方法的匯出同理:

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

我們可以順便再分析下 JS 側拿到 Native 側的配置資訊後做了什麼:

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

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

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

// 匯出genModule到全域性變數global上以便native可以呼叫
global.__fbGenNativeModule = genModule;

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

最後這個 return {name: moduleName,module} 會返回到原生端,原生端會取出 module,再將它構造成 JS 物件給 JS 使用:

Value moduleInfo = m_genNativeModuleJS->call(
      rt,static_cast<double>(result->index));
  CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";

  folly::Optional<Object> module(
      moduleInfo.asObject(rt).getPropertyAsObject(rt,"module"));
複製程式碼

到這裡 JSIExecutor 的初始化完成了,JSBridge 就算搭建完了,以後 Native call JS 都會先後經由 Instance、NativeToJSBridge、JSIExecutor最終到達JS。

載入JS

分析到這裡,原生模組已全部載入完畢,也已將所有原生模組資訊匯出給 JS 側使用,RCTCxxBridge 到 _reactInstance 到 instance 背後的 NativeToJsBridge、JSIExecutor 構建 Bridge 的整個流程也已經全部走完。但是最後一步,JS是怎麼呼叫到原生和原生怎麼給JS回撥的還並未展開,我們先回到 RCTCxxBrige 的 -start 方法:

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

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

// 等待native moudle 和 JS 程式碼載入完畢後就執行JS
  dispatch_group_notify(prepareBridge,^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
複製程式碼
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
  ...
    __weak RCTCxxBridge *weakSelf = self;
    [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error,RCTSource *source) {
      if (error) {
        [weakSelf handleError:error];
        return;
      }
      onSourceLoad(error,source);
    }];
}

+ (void)loadBundleAtURL:(NSURL *)scriptURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)onComplete
{
  // 嘗試同步載入
  int64_t sourceLength;
  NSError *error;
  NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL
                                          runtimeBCVersion:JSNoBytecodeFileFormatVersion
                                              sourceLength:&sourceLength
                                                     error:&error];
  if (data) {
    onComplete(nil,RCTSourceCreate(scriptURL,data,sourceLength));
    return;
  }
  ...
  // 同步載入失敗非同步載入
  if (isCannotLoadSyncError) {
    attemptAsynchronousLoadOfBundleAtURL(scriptURL,onProgress,onComplete);
  } else {
    onComplete(error,nil);
  }
}

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

  if (scriptURL.fileURL) {
    // 非同步載入
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,^{
      NSError *error = nil;
      NSData *source = [NSData dataWithContentsOfFile:scriptURL.path
                                              options:NSDataReadingMappedIfSafe
                                                error:&error];
      onComplete(error,source,source.length));
    });
    return;
  }
}
複製程式碼

實際上 JS 程式碼的載入和底層 Bridge 的建立,都是併發執行的,因為 dispatch_group 的緣故,只有在他們都執行完畢和原生模組初始化完畢後,才會執行 JS。

執行JS

終於到了最後非同步,執行JS程式碼,其實就是把js bundle包經過層層傳遞,最終交給 JavaScriptCore 去執行。

/ RCTCxxBridge.mm
- (void)start
{
   ...
  // 等待”原生模組例項建立完畢、底層Bridge初始化完畢、js bundle包載入完畢“,在JS執行緒執行js原始碼
  dispatch_group_notify(prepareBridge,^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
}
複製程式碼

最後通過executeSourceCode執行程式碼,executeSourceCode 原始碼如下:

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
  // JS bundle包執行完畢回撥
  dispatch_block_t completion = ^{
    // 執行暫存的Native call JS
    [self _flushPendingCalls];
    dispatch_async(dispatch_get_main_queue(),^{
      // 主執行緒傳送通知
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];
      // 開啟定時任務,最終用於驅動JS端定時任務
      [self ensureOnJavaScriptThread:^{
        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
      }];
    });
  };
  // 根據sync來選擇執行JS的方式(同步、非同步)
  if (sync) {
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }
}
複製程式碼

上述程式碼主要做了兩件事情:

  1. 構建回撥,在JS執行完進行回撥。
  2. 執行 JS 程式碼。
- (void)enqueueApplicationScript:(NSData *)script
                             url:(NSURL *)url
                      onComplete:(dispatch_block_t)onComplete
{
  // 底層轉化為在JS執行緒執行JS bundle
  [self executeApplicationScript:script url:url async:YES];
  if (onComplete) {
    _jsMessageThread->runOnQueue(onComplete);
  }
}

複製程式碼

由於底層js bundle最終會在JS執行緒執行,因此 _jsMessageThread->runOnQueue(onComplete) 可以保證先執行完 JS bundle,後執行 onComplete 回撥。

- (void)executeApplicationScript:(NSData *)script
                             url:(NSURL *)url
                           async:(BOOL)async
{
  [self _tryAndHandleError:^{
    NSString *sourceUrlStr = deriveSourceURL(url);
    ...
    // 根據 async 來同步載入、非同步載入
    self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),sourceUrlStr.UTF8String,!async);
  }];
}
複製程式碼
// Instance.cpp
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,std::string sourceURL,bool loadSynchronously) {
    ...
    loadApplication(nullptr,std::move(string),std::move(sourceURL));
}

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

  // 派發到JS執行緒執行js bundle
  runOnExecutorQueue( [...] (JSExecutor* executor) mutable {
      ...
      executor->loadApplicationScript(std::move(*startupScript),std::move(startupScriptSourceURL));
  });
}
複製程式碼

如上,loadApplicationloadApplicationSync 這兩個方法實現基本一致,都是呼叫了成員變數 m_executor的loadApplicationScript 方法,區別在於 loadApplication 把程式碼放到了 m_executorMessageQueueThread 中去執行,而 loadApplicationSync 在當前執行緒執行。

// JSIexecutor.cpp

void JSIExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,std::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","nativeCallSyncHook"),size_t count) { return nativeCallSyncHook(args,count); }));
  // 最終呼叫到JavaScriptCore的JSEvaluateScript函式
  runtime_->evaluateJavaScript(
      std::make_unique<BigStringBuffer>(std::move(script)),sourceURL);
      
  flush();
}
複製程式碼
  1. 通過 setProperty 方法給 global 物件設定了 nativeModuleProxy 的物件。JS 側的 NativeModules 對應的就是 native 側的 nativeModuleProxy。
  2. 向 global 中注入了 nativeFlushQueueImmediate,nativeCallSyncHook 兩個方法。
  3. 呼叫 runtime_->evaluateJavaScript 方法,最終呼叫到 JavaScriptCore 的 JSEvaluateScript 函式,SEvaluateScript 的作用就是在 JS 環境中執行 JS 程式碼。
  4. JS 指令碼執行完成,執行 flush 操作。flush 函式的主要作用就是執行 JS 側的佇列中快取的對 native 的方法呼叫。
void JSIExecutor::flush() {
  // 如果JSIExecutor的flushedQueue_函式不為空,則通過函式flushedQueue_獲取待呼叫的方法queue,然後執行callNativeModules
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_),true);
    return;
  }
  // 以"__fbBatchedBridge"作為屬性key去global中取對應的值也就是batchedBridge,batchedBridge本質上是JS側的MessageQueue類例項化的一個物件
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_,"__fbBatchedBridge");
  if (!batchedBridge.isUndefined()) {
    // 繫結batchedBridge
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_),true);
  } else if (delegate_) {
    // 如果沒有獲取到JS側定義的batchedBridge物件,則直接執行callNativeModules方法,即沒有bind操作。
    callNativeModules(nullptr,true);
  }
}
複製程式碼

BatchedBridge:批處理橋,ReactNative 在處理 JS 和 Native 通訊並非呼叫一次執行一次,而是將呼叫訊息儲存到佇列,在適當的實際進行flush。

flush()

flush 函式有必要著重講一下,在 javaScript 程式碼被執行完畢後,會馬上執行 flush() 函式,flush 內會先判斷 flushedQueue_ 是否存在,flushedQueue_ 如果存在,就會直接執行 callNativeModules 呼叫原生模組的方法,如果不存在,就去 JS 側獲取 batchedBridgebatchedBridge 是什麼,js 側有個 BatchedBridge.js,如下:

// BatchedBridge.js

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

const BatchedBridge: MessageQueue = new MessageQueue();

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

module.exports = BatchedBridge;
複製程式碼

如果 JS 呼叫過 Native,BatchedBridge 就會被初始化,BatchedBridge 物件實際上就是 JS 側的 MessageQueue,在初始化完之後,定義了一個叫 __fbBatchedBridge 的全域性屬性,並把 BatchedBridge 物件作為這個屬性的 value 值,等待著被 JSIExecutor 使用。

再回到 flush() 方法裡面,native 側通過 __fbBatchedBridge 拿到 batchedBridge,先判斷它是否存在,如果不存在,說明 JS 從來沒有呼叫過 Native,callNativeModules 直接傳空,如果存在,就說明 JS 側有呼叫,Native 側就需要進行繫結 JSBridge 的方法,將 JS 的呼叫佇列拿過來進行執行。因為在 JS 被載入的過程中,可能會存在 JS 呼叫 Native,所以這裡提前執行一次 flush,將這些呼叫全部執行。

bindBridge()

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

// JSIExecutor.cpp

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

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

bindBridge一共綁定了四個方法:

  • callFunctionReturnFlushedQueue
  • invokeCallbackAndReturnFlushedQueue
  • flushedQueue
  • callFunctionReturnResultAndFlushedQueue

故名思義,這幾個方法都是由 native 觸發,並把 JS 側的 flushedQueue 返回給 Native 側進行處理。以 callFunctionReturnFlushedQueue 為例,Native 側的 callFunctionReturnFlushedQueue_ 指標指向了 JS 側的 callFunctionReturnFlushedQueue 方法,當呼叫 callFunctionReturnFlushedQueue_ 的時候會直接呼叫到 JS 側,JS 側的方法定義如下:

// MessageQueue.js

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

    return this.flushedQueue();
  }
複製程式碼
// MessageQueue.js

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

    const queue = this._queue;
    this._queue = [[],[],this._callID];
    return queue[0].length ? queue : null;
  }
複製程式碼

JS 會先根據 moduleId 和 methodId 完成呼叫,然後會執行 flushedQueue 將佇列清空,在將其返回給 Nativ e側,Native 側拿到佇列就會繼續執行 JSIExecutor->callNativeModules->ModuleRegistry 那一串流程了。至此整個 JS 執行流程全部走完,開始執行 completion(),程式碼又回到 RCTCxxBridge.mm 的 executeSourceCode:sync: 內:

// RCTCxxBridge.mm

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

然後 bridge 會向外傳送一個名為 RCTJavaScriptDidLoadNotification 的通知,這個通知在哪裡實現呢,可以全域性搜一下我們發現我們又回到了最頂端,RCTRootView 裡。

// RCTRootView.m

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

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

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

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

最後執行到 runApplication,runApplication 內部會呼叫 enqueueJSCall,AppRegistry 是元件名,runApplication 是元件的方法,args 是傳遞的引數,接著會走 RCTCxxBridge->Instance->NativeToJsBridge->JSIExecutor->callFunctionReturnFlushedQueue_ 這一大長串,callFunctionReturnFlushedQueue_ 實際上就是 JS 側的 callFunctionReturnFlushedQueue 方法,JS 側開始執行入口渲染介面,完事返回 flushedQueue 給 Native,Native 再去遍歷 queue 根據 moduleId、methodId 去執行原生模組的方法。至此,整個互動流程結束。

flushedQueue

JS 橋接讓 JS 和 Native 可以直接互動的同時,也會帶來效能損耗的問題。尤其像 ReactNative 這樣的框架,JS 與 Native 的互動非常頻繁,可以想象 scrollView 的滾動,動畫的實現等等,將會帶來非常大的效能開銷。flushedQueue 雖然不能完美的解決這個問題,但是也優化到了極致。JS Call Native 並不是直接呼叫的 Native 方法,而是將呼叫訊息先存放到佇列:

// MessageQueue.js

const MIN_TIME_BETWEEN_FLUSHES_MS = 5;

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

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

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

可以看到 _queue 這個變數 push 進去的模組名和方法名都是 ID,並不是真正的型別,實際上 Native 側存了一個對映表,拿到 JS 側傳過來的 ID 對映到具體的類、方法,通過 NSInvocation 組裝引數傳送訊息。至於為什麼傳 ID 過去,目的是減少通訊資料量,降低溝通成本。另外JS側把這些資料存放到 _queue 後並沒有主動發給 Native 側,而是在適當的時機 Native 過來取,正是通過 bindBridge() 裡面繫結的方法。那有同學可能會問了,這個時機是什麼時候?在我們日常開發中,往往都是隻有事件響應了,才會做程式碼執行,訊息傳遞,這個事件可能是螢幕點選事件,也就可能是 timer 事件、系統事件等等,ReactNative 也是一樣,它的所有 UI 頁面都是 Native 實現,當 Native 有事件觸發就會Call JS,也可能是主動呼叫 JS,這有可能會更新 UI,也有可能是單純出發個事件例如按鈕的點選,待 JS 側處理完業務邏輯後會執行 flushQueue,將訊息佇列返回給 Native 側。這樣做最主要的目的還是為了減少溝通成本,提升橋接效能,這一點與 Cordova 框架的設計幾乎一致。壓縮資料大小加上降低互動頻次,幾乎可以說是優化到極限了

如果 Native 始終不呼叫 JS 側是不是佇列裡面的訊息就一直不會被執行?上面原始碼的 MIN_TIME_BETWEEN_FLUSHES_MS 的常量不知道大家有沒有注意到,每次 enqueueNativeCall 的時候都會拿上一次清空 queue 的時間(flushQueue 或者 invokeCallback 或者執行 nativeFlushQueueImmediate 都會重置這個時間)和現在比較,如果 JS call Native 批量呼叫時間間隔 >= 5毫秒,那就執行一次 nativeFlushQueueImmediate(),這個函式是幹嘛的之前沒有說,我也是追著 JS 側的原始碼才發現這個函式。這也是一個全域性函式,也是 JS 側唯一主動呼叫 Native 側的方法,其他 JS 呼叫 Native 都是被動等 Native 過來取。然後我在全域性搜尋了以下,最終發現這個方法是在 JSIExecutor 進行 loadApplicationScript 的時候給 JS 側注入的,JS 側呼叫這個方法,Native 側就會執行 callNativeModules()把訊息分發到各個模組。

至此,ReactNative 的初始化流程,原生模組的載入,底層 Bridge 的構建,JS程式碼的載入、執行過程,全部分析完畢,後續會著手對 ReactNative UI 渲染層面上繼續分析,分析它是如何動態修改我們的 UI 元件。