技术咨询、项目合作、广告投放、简历咨询、技术文档下载 点击这里 联系博主

# ReactNative与iOS原生通信原理解析-JS加载及执行篇

导语: 其实原本是想编写一篇 react-native (下文简称 rn) 在 iOS 中如何实现 jsbridge 的文章;相信看过官方文档的同学都清楚 rn 和 iOS 通信使用了一个叫RCTBridgeModule的模块去实现。但是不知怎么呢?为了查阅其通信的原理,编写了一篇ReactNative 与 iOS 原生通信原理解析-初始化 (opens new window); 由于篇幅过长,我们还未讲解 JS 代码的加载和执行;下面我们就开始讲解第二个部分【ReactNative 与 iOS 原生通信原理解析-JS 加载及执行篇】。

声明: 本文所使用的 rn 版本为0.63.0;本文篇幅较长,由于涉及到原理所以本文存在大量 RN 的源码,还请谅解。

# 缘起

此篇是上一篇ReactNative 与 iOS 原生通信原理解析-初始化 (opens new window)的姊妹篇,建议对 RN 初始化的整个流程还不清楚的同学可以先行查阅。

上一篇已经讲到在RCTxxBridge.mm的 start 方法中进行了七个步骤:

我们已经详细讲了前面的五个部分;还剩余两个部分:

  • 加载 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 代码加载使用的是 RCTJavaScriptLoaderloadBundleAtURL_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 中实际需要的部分,之后再逐渐按需加载更多的包。来自官网的描述 (opens new window)

  • RAMBundle : RAM bundle 相比普通的 bundle 好处在于可以使用【懒加载】的方式将 module 注入到 JSC 中;RAMBundle 是用 ram-bundle 命令打出来的bundle,它除了生成整合的 js 文件 index.ios.bundle 外,还会生成各个单独的未整合 js 文件,全部放在 js-modules 目录下, bundle 头四个字节固定为 0xFB0BD1E5. RAMBundle 的使用及设置,详情请见官网 (opens new window)

  • 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 的二进制数据.
  2. 否则使用异步加载 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 的两个方法;enqueueApplicationScriptexecuteApplicationScriptSync查阅源码,我们知道这两个方法都是调用的同一个方法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;其实 InstanceNativeToJsBridged 的包装,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 中,创建了一个名为 BatchedBridgeMessageQueue,并设置到 global__fbBatchedBridge 属性里,这个属性后面会用到。在初始化 JS 环境的时候,会加载到某些 NativeModule,这些 module 才会被初始化,即调用到 native 侧 JSINativeModulesgetModule 方法。当相关的 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. 如果 JSIExecutorflushedQueue函数不为空,则通过函数 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 的加载

  1. RN 默认优先判断是否支持本地 Bundle 同步加载;如何可以则:

    • 判断 bundle 是否在本地,因为同步加载只加载本地 bundle;否则直接报错;
    • 使用 fopen 读取本地 bundle;
    • 通过 bundle 的前 4 个字节来判断 bundle 属于什么类型:RAMBundle , String , BCBundle;
    • 返回 bundle 的数据.
  2. 否则使用异步加载 Bundle的方式

    • 如果是本地的 Bundle 则使用异步加载本地的方式;
    • 如果不是本地 Bundle 则实例化一个RCTMultipartDataTask下载任务;异步下载 Bundle;

JSBundle 的加载

JSBundle 的加载和运行较为复杂,其主要步骤如下:

  1. 代码的执行统一使用了 RCTxxBridge 中的executeSourceCode去执行;其内部统一使用了 executeApplicationScript 去处理;

  2. RCTxxBridge 拥有 reactInstance; 利用其实例去执行对应的 jsbundle;然而我们之前也已经讲到 reactInstance 会初始化 NativeToJsBridge;NativeToJsBridge 会利用 factory 初始化 JSIExecutor;其实 InstanceNativeToJsBridged 的包装,NativeToJsBridge 又是 JSIExecutor 的包装 所以最终会使用JSIExecutorloadBundle 去加载;

  1. 在 JSIExecutor 中的 loadBundle 会使用 JSCRuntime 去利用 JavaScriptCore 在 iOS 程序中取执行 js 代码;

  2. 执行 js 代码的之前会初始化整个 JS 的上下文 JSContext;并执行 js 和 native 通信所需的 js 代码;

  3. 执行完成 js 代码后会调用 flush 方法,去调用 native modules;

  • 由于初始化了 JSContext,BatchedBridge.js,NativeModules.js 中的初始化代码也会执行。在 BatchedBridge.js 中,创建了一个名为 BatchedBridge 的 MessageQueue,并设置到 global 的__fbBatchedBridge 属性里;

  • 如果 JSIExecutorflushedQueue函数不为空,则通过函数 flushedQueue 获取待调用的方法 queue,然后执行 callNativeModules;

  • 通过__fbBatchedBridge(在上一篇已经讲过)作为属性 key 去 global 中取对应的值也就是 batchedBridge,batchedBridge 本质上是 JS 侧的 MessageQueue 类实例化的一个对象;

  • 如果获取到的 batchedBridge 为空或者还未绑定,则先将 js 函数和 native 进行绑定,然后执行 callNativeModules;

  • callNativeModules的主要作用其实就是通过 invoke 的方式执行 native modules 中方法。

现在我们已经清楚了 RN 的初始化以及 JS 加载和执行的整个流程;是时候着手下一部分ReactNative 与 iOS 原生通信原理解析-通信篇 (opens new window)去理解 js 如何和 native 通信了。

ReactNative 与 iOS 原生通信原理解析系列

【未经作者允许禁止转载】 Last Updated: 9/23/2024, 11:39:31 AM