天天看點

jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇

jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇

原文位址:ReactNative與iOS原生通信原了解析-JS加載及執行篇

導語: 其實原本是想編寫一篇

react-native

(下文簡稱 rn) 在

iOS

中如何實作

jsbridge

的文章;相信看過官方文檔的同學都清楚 rn 和 iOS 通信使用了一個叫

RCTBridgeModule

的子產品去實作。但是不知怎麼呢?為了查閱其通信的原理,編寫了一篇 ReactNative 與 iOS 原生通信原了解析-初始化 ; 由于篇幅過長,我們還未講解 JS 代碼的加載和執行;下面我們就開始講解第二個部分【ReactNative 與 iOS 原生通信原了解析-JS 加載及執行篇】。

聲明: 本文所使用的 rn 版本為

0.63.0

;本文篇幅較長,由于涉及到原理是以本文存在大量 RN 的源碼,還請諒解。

緣起

此篇是上一篇

ReactNative 與 iOS 原生通信原了解析-初始化

的姊妹篇,建議對 RN 初始化的整個流程還不清楚的同學可以先行查閱。

上一篇已經講到在

RCTxxBridge.mm

的 start 方法中進行了七個步驟:

jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇

我們已經詳細講了前面的五個部分;還剩餘兩個部分:

  • 加載 js 代碼
  • 執行 js 代碼

下面我們就來看看,RN 是如何加載 JS 代碼和執行 JS 代碼的。

加載 JS 代碼

讓我們再來回顧一下

RCTxxBridge.mm

的 start 方法:

```java
// RCTxxBridge.mm

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

  //2. 提前設定并開啟JS線程 _jsThread
  _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
  _jsThread.name = RCTJSThreadName;
  _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
  _jsThread.stackSize *= 2;
#endif
  // 啟動JS線程
  [_jsThread start];

  dispatch_group_t prepareBridge = dispatch_group_create();
  //3. 注冊native modules,主要是在初始化RCTBridge使用initWithBundleURL_moduleProvider_launchOptions中的moduleProvider block傳回值的native modules;
  [self registerExtraModules];
  // 重點:注冊所遇的自定義Native Module;包括你在rn官網上看到的原生子產品定義以及RN自帶的Text,Date等原生元件
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
    // 初始化所有懶加載的native module

  [self registerExtraLazyModules];

  // 其實這裡不會做任何事情,詳情請見initializeBridge
  _reactInstance.reset(new Instance);

  __weak RCTCxxBridge *weakSelf = self;

  //4. 準備executor factory; 看RCTBridge是否指定了executorClass
  std::shared_ptr<JSExecutorFactory> executorFactory;
  if (!self.executorClass) {// 如果沒有指定executorClass 但是實作了RCTCxxBridgeDelegate協定,那麼就使用jsExecutorFactoryForBridge的方式 準備 executor factory 否則就使用make_shared初始化一個空的JSCExecutorFactory
    if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
      id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>)self.delegate;
      executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
    }
    if (!executorFactory) {
      executorFactory = std::make_shared<JSCExecutorFactory>(nullptr);
    }
  } else {// 如果指定了 executorClass  就使用指定的executorClass 初始化;一般RCTObjcExecutorFactory為開發環境使用的
    id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
    executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
      if (error) {
        [weakSelf handleError:error];
      }
    }));
  }

  // 5. module初始化完成就初始化底層Instance執行個體,也就是_reactInstance
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
    // 利用executorFactory來initializeBridge 方法;完成初始化_reactInstance(也就是Instance)
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];

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

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

  // 7. 等待native moudle 和 JS 代碼加載完畢後就執行JS; dispatch_group_t和dispatch_group_notify聯合使用保證異步代碼同步按順序執行
  dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      // 重點,執行JS代碼;後面我們會具體展開分析
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
}
// 真正的加載邏輯
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge];

    // 聲明加載成功的回調
  RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) {

    // 加載成功之後發送RCTBridgeDidDownloadScriptNotification通知RCTRootView準備執行JavaScript中的方法AppRegistry.runApplication
    NSDictionary *userInfo = @{
      RCTBridgeDidDownloadScriptNotificationSourceKey : source ?: [NSNull null],
      RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey : self->_bridgeDescription ?: [NSNull null],
    };

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

    _onSourceLoad(error, source);
  };

  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 if (!self.bundleURL) {

    onSourceLoad(error, nil);
  } else {
      // 重點來了,使用RCTJavaScriptLoader異步加載JS代碼
    __weak RCTCxxBridge *weakSelf = self;
    [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL
                              onProgress:onProgress
                              onComplete:^(NSError *error, RCTSource *source) {
                                if (error) {
                                  [weakSelf handleError:error];
                                  return;
                                }
                                onSourceLoad(error, source);
                              }];
  }
}

           

通過上面的部分我們了解到 RN 代碼加載使用的是

RCTJavaScriptLoader

loadBundleAtURL_onProgress_onComplete

的方法;我們可以使用 onProgress 監聽加載進度,在

onComplete

中根據 error 是否為空判斷加載是否成功;并且可以使用

source.data

擷取到加載的二進制 JS 代碼。

那麼 RCTJavaScriptLoader 又是如何加載的?

// RCTJavaScriptLoader.mm
+ (void)loadBundleAtURL:(NSURL *)scriptURL
             onProgress:(RCTSourceLoadProgressBlock)onProgress
             onComplete:(RCTSourceLoadBlock)onComplete
{
  int64_t sourceLength;
  NSError *error;
  // 嘗試使用同步加載的方式加載jsbundle
  NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL
                                          runtimeBCVersion:JSNoBytecodeFileFormatVersion
                                              sourceLength:&sourceLength
                                                     error:&error];
  if (data) {
    onComplete(nil, RCTSourceCreate(scriptURL, data, sourceLength));
    return;
  }

  const BOOL isCannotLoadSyncError = [error.domain isEqualToString:RCTJavaScriptLoaderErrorDomain] &&
      error.code == RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously;
    // 否則嘗試使用異步加載jsbundle
  if (isCannotLoadSyncError) {
    attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
  } else {
    onComplete(error, nil);
  }
}

           

從上面的代碼我們知道,jsbundle 的加載分為兩種情況:

  • 同步加載
  • 異步加載

預設的情況,會嘗試使用同步加載的方式,如果同步加載失敗則使用異步加載的方式。

同步加載 JS 及三種 Bundle

由于同步加載代碼較長筆者暫且保留重要部分,有興趣同學可自行查閱。

+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
                               runtimeBCVersion:(int32_t)runtimeBCVersion
                                   sourceLength:(int64_t *)sourceLength
                                          error:(NSError **)error
{
  // ...  此處部分進行了scriptURL非空判斷
  // 如果bundle不再本地,那麼就報錯,不能同步加載Bundle
  if (!scriptURL.fileURL) {
    if (error) {
      *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
                                   code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
                               userInfo:@{
                                 NSLocalizedDescriptionKey :
                                     [NSString stringWithFormat:@"Cannot load %@ URLs synchronously", scriptURL.scheme]
                               }];
    }
    return nil;
  }

  // 通過bundle的前4個位元組,可以判斷出目前的bundle是普通的Bundle還是RAM bundle(RAM bundle前四個位元組的值為0xFB0BD1E5)
  // RAM bundle 相比普通的bundle好處在于可以使用【懶加載】的方式将 module注入到JSC中
  // 使用fopen讀取檔案
  FILE *bundle = fopen(scriptURL.path.UTF8String, "r");
  if (!bundle) {
    if (error) {
      *error = [NSError
          errorWithDomain:RCTJavaScriptLoaderErrorDomain
                     code:RCTJavaScriptLoaderErrorFailedOpeningFile
                 userInfo:@{
                   NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error opening bundle %@", scriptURL.path]
                 }];
    }
    return nil;
  }
  // 讀取header
  facebook::react::BundleHeader header;
  size_t readResult = fread(&header, sizeof(header), 1, bundle);
  // 檔案讀取之後記得關閉哦
  fclose(bundle);
  // ....
  // 通過header就可以知道是什麼類型的Bundle了(請見下面的pareseTyoeFromHeader)
  facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header);
  switch (tag) {
    case facebook::react::ScriptTag::RAMBundle:
      break;

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

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

  struct stat statInfo;
  if (sourceLength) {
    *sourceLength = statInfo.st_size;
  }
  // 傳回jsbundle的二進制資料
  return [NSData dataWithBytes:&header length:sizeof(header)];
}

// JSBundleType.cpp

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

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

           

通過上面的代碼我們知道同步加載

jsbundle

一共做了 4 件事情:

  1. 判斷

    bundle

    是否在本地,因為同步加載隻加載本地 bundle;否則直接報錯;
  2. 使用

    fopen

    讀取本地 bundle;
  3. 通過

    bundle

    的前 4 個位元組來判斷 bundle 屬于什麼類型:

    RAMBundle

    ,

    String

    ,

    BCBundle

    ;
  4. 傳回

    bundle

    的二進制資料.

讀到這裡,四個步驟是不是對

RAMBundle

,

String

,

BCBundle

這三種類型有疑問,他們分别是幹嘛的;筆者就在此處給大家解答疑惑。

正常情況下我們使用

react-native bundle

打的包是普通的包也就是

String

類型;如果你使用

react-native ram-bundle

則是打的 RAMBundle;

那麼相比普通的

Bundle

,

RAMBundle

有什麼好處呢?

react-native

執行 JS 代碼之前,必須将代碼加載到記憶體中并進行解析。如果你加載了一個 50MB 的普通 Bundle,那麼所有的 50MB 都必須被加載和解析才能被執行。RAM 格式的 Bundle 則對此進行了優化,即啟動時隻加載 50MB 中實際需要的部分,之後再逐漸按需加載更多的包。來自 官網的描述
  • RAMBundle

    :

    RAM bundle

    相比普通的

    bundle

    好處在于可以使用【懶加載】的方式将 module 注入到 JSC 中;RAMBundle 是用 ram-bundle 指令打出來的

    bundle

    ,它除了生成整合的 js 檔案

    index.ios.bundle

    外,還會生成各個單獨的未整合 js 檔案,全部放在

    js-modules

    目錄下, bundle 頭四個位元組固定為

    0xFB0BD1E5

    .
    RAMBundle 的使用及設定,詳情請見 官網
  • BCBundle

    :

    BCBundle

    是 js 位元組碼

    bundle

    類型; 不允許使用;
  • String

    : 普通的 bundle

異步加載 jsbundle

上面介紹了同步加載 bundle 就是讀取本地磁盤預置或預先下載下傳的 bundle 資料,是以不難判斷異步加載 bundle 就是下載下傳網絡上的 bundle。下面我們來看下源碼:

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

          // ... 處理下載下傳的異常請見
          onComplete(error, nil);
          return;
        }

        // 對于有多個請求頭的,包含X-Http-Status請求頭判斷其值是否為200,如果不是則直接報錯
        NSString *statusCodeHeader = headers[@"X-Http-Status"];
        if (statusCodeHeader) {
          statusCode = [statusCodeHeader integerValue];
        }

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

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

果不其然,異步加載主要做了兩件事情:

  1. 如果是本地的 Bundle 則使用異步加載本地的方式;
  2. 如果不是本地 Bundle 則執行個體化一個

    RCTMultipartDataTask

    下載下傳任務;異步下載下傳 Bundle;

對于 Bundle 的下載下傳相信講到這裡大家已經了然于心;在這裡簡單做一個總結:

  1. RN 預設 優先判斷是否支援本地 Bundle

    同步加載

    ;如何可以則:
  • 判斷

    bundle

    是否在本地,因為同步加載隻加載本地 bundle;否則直接報錯;
  • 使用

    fopen

    讀取本地 bundle;
  • 通過

    bundle

    的前 4 個位元組來判斷 bundle 屬于什麼類型:

    RAMBundle

    ,

    String

    ,

    BCBundle

    ;
  • 傳回

    bundle

    的二進制資料.
  1. 否則使用 異步加載 Bundle 的方式
  • 如果是本地的 Bundle 則使用異步加載本地的方式;
  • 如果不是本地 Bundle 則執行個體化一個

    RCTMultipartDataTask

    下載下傳任務;異步下載下傳 Bundle;

加載完成 Bundle 就該執行 JS 代碼咯。

執行 JS 代碼

在 RCTxxBridge 的 start 方法中;js 代碼的執行需要等到 jsbundle 以及加載且 native modules 也已加載完成才會進行執行。

// RCTxxBridge.mm

// 等待 jsbundle的和native modules完成加載後則開始執行代碼
 dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
  // js代碼的執行
  - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync{
  // js代碼執行回調
  dispatch_block_t completion = ^{

    // 當js代碼執行完成,需要重新整理js執行事件隊列
    [self _flushPendingCalls];

    // 在主線程中通知RCTRootView; js代碼已經執行完畢;當RCTRootView接收到通知就會挂在并展示
    dispatch_async(dispatch_get_main_queue(), ^{
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
                                                          object:self->_parentBridge
                                                        userInfo:@{@"bridge" : self}];

      [self ensureOnJavaScriptThread:^{
        // 定時器繼續執行
        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
      }];
    });
  };

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

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

通過上面我們簡單了解到:

  • 根據 sync 來選擇是同步執行 js 還是異步執行 js;
  • js 執行完成之後會進入事件回調 completion;在事件回調中我們會重新整理目前的 js 執行隊列并發送通知給 RCTRootView;

上面講到同步執行 js 和異步執行 js 的兩個方法;

enqueueApplicationScript

executeApplicationScriptSync

查閱源碼,我們知道這兩個方法都是調用的同一個方法

executeApplicationScript

;

// RCTxxBridge.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:方法
    // 還記得嗎reactInstance就是我們在上一篇文章中講到的Instance初始化;
    auto reactInstance = self->_reactInstance;
    if (isRAMBundle(script)) {
      [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
      auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
      std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
      [self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad];
      [self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize];
      if (reactInstance) {
        auto registry =
            RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
        reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async);
      }
    } else if (reactInstance) {
      reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script), sourceUrlStr.UTF8String, !async);
    } else {
      std::string methodName = async ? "loadBundle" : "loadBundleSync";
      throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
    }
  }];
}
           

如果您看了上一篇的 RN 初始化文章,是不是豁然開朗;js 代碼的執行利用的是

_reactInstance->loadRAMBundle

或者

reactInstance->loadScriptFromString

方法;

然而我們之前也已經講到

reactInstance

會初始化

NativeToJsBridge

;

NativeToJsBridge

會利用 factory 初始化

JSIExecutor

;其實

Instance

NativeToJsBridged

的包裝,NativeToJsBridge

又是

JSIExecutor

的包裝

;

為了證明這一點,我們一起來看一下具體的源碼:(此處拿

loadScriptFromString

為例)

//Instance.cpp
void Instance::loadScriptFromString(
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL,
    bool loadSynchronously) {
  SystraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
  if (loadSynchronously) {
    // 同步加載Bundle
    loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
  } else {
    // 異步加載Bundle
    loadBundle(nullptr, std::move(string), std::move(sourceURL));
  }
}
void Instance::loadBundle(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL) {
  callback_->incrementPendingJSCalls();
  SystraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
  // 最終還是調用的NativeToJsBridge的加載方法
  nativeToJsBridge_->loadBundle(
      std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}

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

    // 最終還是調用的NativeToJsBridge的加載方法
  nativeToJsBridge_->loadBundleSync(
      std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}

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

           

相信看到這裡已經證明了筆者上面所說的觀點;下面我們就來具體看看 JSIExecutor 中的加載方法:

// JSIExecutor.cpp
void JSIExecutor::loadBundle(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {
  // 沒錯執行就是這麼簡單粗暴,使用了JSCRuntime的執行js方法
  runtime_->evaluateJavaScript(
      std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
  // 不要忘記還有一個重新整理的操作
  flush();
}
// 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;
  // 使用JavaScriptCore執行js代碼
  JSValueRef res =
      JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
  JSStringRelease(sourceRef);
  if (sourceURLRef) {
    JSStringRelease(sourceURLRef);
  }
  checkException(res, exc);
  return createValue(res);
}
           

在 ctx 中執行 JS 源碼後,會初始化 JS 環境,

BatchedBridge.js

,

NativeModules.js

中的初始化代碼也會執行。在

BatchedBridge.js

中,建立了一個名為

BatchedBridge

MessageQueue,并設定到

global

__fbBatchedBridge

屬性裡,這個屬性後面會用到。在初始化 JS 環境的時候,會加載到某些

NativeModule

,這些 module 才會被初始化,即調用到 native 側

JSINativeModules

getModule

方法。當相關的

Module

都加載完之後,evaluateScript

方法執行完,JS 環境初始化完畢。

不知您注意到在 JSIExecutor 執行 jsbundle 之後有一個 flush 方法沒?

flush 方法

flush 方法中内容比較多。

void JSIExecutor::flush() {
  // 如果JSIExecutor的flushedQueue_函數不為空,則通過函數flushedQueue_擷取待調用的方法queue,然後執行callNativeModules
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_), true);
    return;
  }

  // 通過__fbBatchedBridge(在上一篇已經講過)作為屬性key去global中取對應的值也就是batchedBridge,batchedBridge本質上是JS側的MessageQueue類執行個體化的一個對象
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
  // 如果 js側的batchedBridge對象為空(表示還未有任何Native modules被執行),那麼就執行bindBridge操作; bindBridge的主要工作是将js側的屬性/方法綁定給native,以便于後續Native調用js方法
  if (!batchedBridge.isUndefined()) {
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    callNativeModules(nullptr, true);
  }
}
// 各種js方法向native的綁定
void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    // 通過js側的__fbBatchedBridge擷取對應的batchedBridge
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      throw JSINativeException(
          "Could not get BatchedBridge, make sure your bundle is packaged correctly");
    }
// 把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor對象的callFunctionReturnFlushedQueue_進行綁定
    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
  // 把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_進行綁定;
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
  // 把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_進行綁定。
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
  });
}

void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}


// BatchedBridge.js
const MessageQueue = require("./MessageQueue");

const BatchedBridge: MessageQueue = new MessageQueue();

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

module.exports = BatchedBridge;
           

總結一下 flush 方法的作用:

  1. 在 ctx 中執行 JS 源碼,會初始化 JS 環境,

    BatchedBridge.js,NativeModules.js

    中的初始化代碼也會執行。在

    BatchedBridge.js

    中,建立了一個名為 BatchedBridge 的 MessageQueue,并設定到 global 的

    __fbBatchedBridge

    屬性裡;
  2. 如果

    JSIExecutor

    flushedQueue

    函數不為空,則通過函數

    flushedQueue

    擷取待調用的方法 queue,然後執行

    callNativeModules;

  3. 通過

    __fbBatchedBridge

    (在上一篇已經講過)作為屬性 key 去 global 中取對應的值也就是

    batchedBridge,batchedBridge

    本質上是 JS 側的

    MessageQueue

    類執行個體化的一個對象;
  4. 如果擷取到的 batchedBridge 為空或者還未綁定,則先将 js 函數和 native 進行綁定,然後執行

    callNativeModules

    ;
  5. callNativeModules

    的主要作用其實就是通過

    invoke

    的方式執行 native modules 中方法。
// 根據callNativeModules追溯到,後面會調用ModuleRegistry
// ModuleRegistry.cpp
void ModuleRegistry::callNativeMethod(
    unsigned int moduleId,
    unsigned int methodId,
    folly::dynamic &&params,
    int callId) {
  modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
           

總結

至此,JSBundle 的加載以及執行以及講解完畢,現在我們來總結一下各個部分都做了什麼。

JSBundle 的加載
jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇
  1. RN 預設 優先判斷是否支援本地 Bundle

    同步加載

    ;如何可以則:
  • 判斷

    bundle

    是否在本地,因為同步加載隻加載本地 bundle;否則直接報錯;
  • 使用

    fopen

    讀取本地 bundle;
  • 通過

    bundle

    的前 4 個位元組來判斷 bundle 屬于什麼類型:

    RAMBundle

    ,

    String

    ,

    BCBundle

    ;
  • 傳回

    bundle

    的資料.
  1. 否則使用 異步加載 Bundle 的方式
  • 如果是本地的 Bundle 則使用異步加載本地的方式;
  • 如果不是本地 Bundle 則執行個體化一個

    RCTMultipartDataTask

    下載下傳任務;異步下載下傳 Bundle;
JSBundle 的加載
jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇

JSBundle 的加載和運作較為複雜,其主要步驟如下:

  1. 代碼的執行統一使用了

    RCTxxBridge

    中的

    executeSourceCode

    去執行;其内部統一使用了

    executeApplicationScript

    去處理;
  2. RCTxxBridge

    擁有

    reactInstance

    ; 利用其執行個體去執行對應的

    jsbundle

    ; 然而我們之前也已經講到

    reactInstance

    會初始化

    NativeToJsBridge

    ;

    NativeToJsBridge

    會利用 factory 初始化

    JSIExecutor

    ;其實

    Instance

    NativeToJsBridged

    的包裝,NativeToJsBridge

    又是

    JSIExecutor

    的包裝 是以最終會使用

    JSIExecutor

    loadBundle

    去加載;
  3. 在 JSIExecutor 中的 loadBundle 會使用 JSCRuntime 去利用 JavaScriptCore 在 iOS 程式中取執行 js 代碼;
  4. 執行 js 代碼的之前會初始化整個 JS 的上下文 JSContext;并執行 js 和 native 通信所需的 js 代碼;
  5. 執行完成 js 代碼後會調用 flush 方法,去調用 native modules;
  • 由于初始化了 JSContext,

    BatchedBridge.js,NativeModules.js

    中的初始化代碼也會執行。在

    BatchedBridge.js

    中,建立了一個名為 BatchedBridge 的 MessageQueue,并設定到 global 的

    __fbBatchedBridge

    屬性裡;
  • 如果

    JSIExecutor

    flushedQueue

    函數不為空,則通過函數

    flushedQueue

    擷取待調用的方法 queue,然後執行

    callNativeModules;

  • 通過

    __fbBatchedBridge

    (在上一篇已經講過)作為屬性 key 去 global 中取對應的值也就是

    batchedBridge,batchedBridge

    本質上是 JS 側的

    MessageQueue

    類執行個體化的一個對象;
  • 如果擷取到的

    batchedBridge

    為空或者還未綁定,則先将 js 函數和 native 進行綁定,然後執行

    callNativeModules

    ;
  • callNativeModules

    的主要作用其實就是通過

    invoke

    的方式執行 native modules 中方法。

現在我們已經清楚了 RN 的初始化以及 JS 加載和執行的整個流程;是時候着手下一部分

ReactNative 與 iOS 原生通信原了解析-通信篇

去了解 js 如何和

native

通信了。

ReactNative 與 iOS 原生通信原了解析系列
  • ReactNative 與 iOS 原生通信原了解析-初始化
  • ReactNative 與 iOS 原生通信原了解析-JS 加載及執行篇
  • ReactNative 與 iOS 原生通信原了解析-通信篇
jsbridge原理_ReactNative與iOS通信原了解析-JS加載及執行篇