iOS RN中是如何初始化JS执行环境的

611 阅读6分钟

iOS RN中是如何初始化JS执行环境的

RCTCxxBridge-start方法执行时, 有以下几个关键方法:

- (void)start {
    ...

    // 1. 创建JS线程, 开启 JSThread 的线程 Runloop
    _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
    _jsThread.name = RCTJSThreadName; //  @"com.facebook.react.JavaScript";
    _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
    [_jsThread start];

    // 2. native module 初始化, 包装到 RCTModuleData 中
    ...

    // 3. 准备CxxReactInstnace, 准备 JSCE, 为React JS的Cxx层的环境做准备
    _reactInstance.reset(new Instance);
    std::shared_ptr<JSExecutorFactory> executorFactory;
    auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr);
    executorFactory = std::make_shared<JSCExecutorFactory>(installBindings);
    
    // 4. 真正的JS 环境初始化的方法 _initializeBridge

    dispatch_group_enter(prepareBridge);
    __weak RCTCxxBridge *weakSelf = self;
    [self ensureOnJavaScriptThread:^{
        [weakSelf _initializeBridge:executorFactory];
        dispatch_group_leave(prepareBridge);
    }];

    ...
}

本文会梳理其中的几块内容:

  1. JS Thread 的创建, 以及开启 JS Thread 的 Runloop
  2. RN启动时, 在JS Thread中, 构造 JSE 环境的过程!!!

1. JS Thread 的构造, 启动 与 JSThread 中执行block

// RCTCxxBridge 启动方法
- (void)start {
    ...

    _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
    _jsThread.name = RCTJSThreadName; //  @"com.facebook.react.JavaScript";
    _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
    [_jsThread start];

    ...

    [self ensureOnJavaScriptThread:^{
        ...
    }];
}

// JS Thread 启动函数入口 - 主要是JS Thread 线程保活
+ (void)runRunLoop {
  @autoreleasepool {
    pthread_setname_np([NSThread currentThread].name.UTF8String);

    // Set up a dummy runloop source to avoid spinning
    CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
    CFRelease(noSpinSource);

    while (kCFRunLoopRunStopped !=
           CFRunLoopRunInMode(
               kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
      RCTAssert(NO, @"not reached assertion"); 
    }
  }
}

// JS Thread 的任务调度API
- (void)ensureOnJavaScriptThread:(dispatch_block_t)block {
    RCTAssert(_jsThread, @"This method must not be called before the JS thread is created");

    // This does not use _jsMessageThread because it may be called early before the runloop reference is captured
    // and _jsMessageThread is valid. _jsMessageThread also doesn't allow us to shortcut the dispatch if we're
    // already on the correct thread.

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

// 任务调度分发
- (void)_tryAndHandleError:(dispatch_block_t)block {
  NSError *error = tryAndReturnError(block);
  if (error) {
    [self handleError:error];
  }
}

// 真实的执行与异常错误处理
NSError *tryAndReturnError(const std::function<void()> &func) {
  try {
    @try {
      func();
      return nil;
    } @catch (NSException *exception) {
      return RCTErrorWithNSException(exception); // OC层的 exception
    } @catch (id exception) {
      // This will catch any other ObjC exception, but no C++ exceptions
      return RCTErrorWithMessage(@"non-std ObjC Exception");
    }
  } catch (const std::exception &ex) {
    return errorWithException(ex);
  } catch (...) {
    // On a 64-bit platform, this would catch ObjC exceptions, too, but not on
    // 32-bit platforms, so we catch those with id exceptions above.
    return RCTErrorWithMessage(@"non-std C++ exception");
  }
}

// 出错以后, Error 提示与资源清理
- (void)handleError:(NSError *)error {
  // This is generally called when the infrastructure throws an
  // exception while calling JS.  Most product exceptions will not go
  // through this method, but through RCTExceptionManager.

  // There are three possible states:
  // 1. initializing == _valid && _loading
  // 2. initializing/loading finished (success or failure) == _valid && !_loading
  // 3. invalidated == !_valid && !_loading

  // !_valid && _loading can't happen.

  // In state 1: on main queue, move to state 2, reset the bridge, and RCTFatal.
  // In state 2: go directly to RCTFatal.  Do not enqueue, do not collect $200.
  // In state 3: do nothing.

  if (self->_valid && !self->_loading) {
    if ([error userInfo][RCTJSRawStackTraceKey]) {
      [self.redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
    }

    RCTFatal(error);

    // RN will stop, but let the rest of the app keep going.
    return;
  }

  if (!_valid || !_loading) {
    return;
  }

  // Hack: once the bridge is invalidated below, it won't initialize any new native
  // modules. Initialize the redbox module now so we can still report this error.
  RCTRedBox *redBox = [self redBox];

  _loading = NO;
  _valid = NO;
  _moduleRegistryCreated = NO;

  dispatch_async(dispatch_get_main_queue(), ^{
    if (self->_jsMessageThread) {
      // Make sure initializeBridge completed
      self->_jsMessageThread->runOnQueueSync([] {});
    }

    self->_reactInstance.reset();
    self->_jsMessageThread.reset();

    [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification object:self->_parentBridge userInfo:@{@"bridge" : self, @"error" : error}];

    if ([error userInfo][RCTJSRawStackTraceKey]) {
      [redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
    }

    RCTFatal(error);
  });
}

以上JSThread关联的逻辑主要包括两块:

  1. JSThread 的创建与启动: 直接在RCTCxxBridge start方法中创建
  2. JSThread 启动入口函数: 在+runRunLoop作为 JSThread 的入口. 其中给 JSThreadRunloop启动, 并增加一个 RunloopSource, 保活
  3. 如何将block 调度到JSThread中执行: 使用-ensureOnJavaScriptThread方法
  4. JSThread中调度执行的block出现错误的分级处理: OC层错误, C++层错误...
  5. JSThread中任务执行错误时, 资源清理. 会用到_jsMessageThread

2. RN构造JS的初始执行环境

前面创建了OC层的JS线程(JSThread)并进行了保活处理. 下面会在这个JSThread中进行react cxx bridge初始化.

在理解代码之前, 需要先理解如下几个cxx类, 其中react 在cxx层的实现, 后面我们称为react-cxx:

  1. std::shared_ptr<Instance> _reactInstance: 是react-cxxcxx 层的实例对象, 通过RCTCxxBridge持有, 大量的JS层调用方法都是通过它
  2. std::shared_ptr<JSExecutorFactory> executorFactory: JSExecutorFactory是 JSE 的工厂类型, 在Native 调用 JS时, 用来创建JSExcutor
  3. std::shared_ptr<RCTMessageThread> _jsMessageThread: 是 JSThread的cxx包装类, 方便cxx层的对象通过JSThread调度方法
  4. std::shared_ptr<ModuleRegistry>: react-cxxcxx 层中包装的ModuleRegistry, JS-Content初始化的很大一部分就是, setup 这个对象

下面就是react-cxx初始化的核心代码:

// 1. RCTCxxBridge 中的`_reactInstance` 对象会持有 react-cxx instance对象, 它是一个cxx类!!!
- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory {
    if (!self.valid) {
        return;
    }

    // 将 JSThread 包装到 一个 cxx 类 RCTMessageThread 中!!! 让 _reactInstance 能方便使用
    __weak RCTCxxBridge *weakSelf = self;    
    _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
        if (error) {
            [weakSelf handleError:error];
        }
    });

    // This can only be false if the bridge was invalidated before startup completed
    if (_reactInstance) {
        [self _initializeBridgeLocked:executorFactory];
    }
}

- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory {
    std::lock_guard<std::mutex> guard(_moduleRegistryLock);
    // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
    _reactInstance->initializeBridge(
                                     std::make_unique<RCTInstanceCallback>(self),
                                     executorFactory,
                                     _jsMessageThread,
                                     [self _buildModuleRegistryUnlocked]);

    _moduleRegistryCreated = YES;
}

- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked {
    if (!self.valid) {
        return {};
    }

    __weak __typeof(self) weakSelf = self;
    ModuleRegistry::ModuleNotFoundCallback moduleNotFoundCallback = ^bool(const std::string &name) {
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        return [strongSelf.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] &&
        [strongSelf.delegate bridge:strongSelf didNotFindModule:@(name.c_str())];
    };

    auto registry = std::make_shared<ModuleRegistry>(
        createNativeModules(_moduleDataByID, self, _reactInstance),
        moduleNotFoundCallback);
    return registry;
}

以上代码的重要内容:

  1. 准备工作1: JSThread包装. _initializeBridge中, 包装成RCTMessageThread, 并赋值给RCTCxxBridge._jsMessageThread.
  2. 准备工作2: 创建ModuleRegistry. _buildModuleRegistryUnlocked构造std::shared_ptr<ModuleRegistry>
  3. setup react-cxx: 进行初始化时调用Instance::initializeBridge(...)

2.1 JSThread的cxx包装类RCTMessageThread

RN为了react-cxx在使用JSThread的方便, 会创建一个cxx对象_jsMessageThread, 它会持有JSThread.runloop, 然后对外提供类似dispatch_async,dispatch_sycn的方法runOnQueuerunOnQueueSync.

class MessageQueueThread {
 public:
  virtual ~MessageQueueThread() {}
  virtual void runOnQueue(std::function<void()> &&) = 0;
  // runOnQueueSync and quitSynchronous are dangerous.  They should only be
  // used for initialization and cleanup.
  virtual void runOnQueueSync(std::function<void()> &&) = 0;
  // Once quitSynchronous() returns, no further work should run on the queue.
  virtual void quitSynchronous() = 0;
};

class RCTMessageThread : public MessageQueueThread,
                         public std::enable_shared_from_this<RCTMessageThread> {
 public:                   
  RCTMessageThread(NSRunLoop *runLoop, RCTJavaScriptCompleteBlock errorBlock);
  ~RCTMessageThread() override;
                             
  void runOnQueue(std::function<void()> &&) override;
  void runOnQueueSync(std::function<void()> &&) override;
  void quitSynchronous() override;
                             
  void setRunLoop(NSRunLoop *runLoop);

 private:
  void tryFunc(const std::function<void()> &func);
  // 私有方法: 模拟 dispatch_async
  void runAsync(std::function<void()> func);
  // 私有方法: 模拟 dispatch_sync
  void runSync(std::function<void()> func);

  CFRunLoopRef m_cfRunLoop; 
  RCTJavaScriptCompleteBlock m_errorBlock;
  std::atomic_bool m_shutdown;
};

2.2 RCTModuleData包装cxx对象, 然后注入到ModuleRegistry

在创建ModuleRegistry时, 需要std::vector<std::unique_ptr<NativeModule>> modules对象.

RN提供了以下方法, 将RCTModuleData包装到cxx类NativeModule, 具体方法如下:

std::vector<std::unique_ptr<NativeModule>> createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance) {    
    // NativeModule 会有两个实现类型:

    // RCTNativeModule 和 RCTCxxModule
  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;
}

上面代码中, NativeModule可以认为只是一个interface, 实际我们的具体实现有两个类型:

  1. RCTCxxModule: 没遇到过...
  2. RCTNativeModule: 这个就是我们常规的, 实现@protocol RCTModuleBridge的OC native module

对于RCTNativeModule, 可以简单看一下它的接口, 实际就是一个cxx的针对RCTModuleData的包装

class RCTNativeModule : public NativeModule {
 public:
  RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData);

  std::string getName() override;
  std::string getSyncMethodName(unsigned int methodId) override;
  std::vector<MethodDescriptor> getMethods() override;
  folly::dynamic getConstants() override;

  void invoke(unsigned int methodId, folly::dynamic &&params, int callId) override;
  MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&params) override;

 private:
  __weak RCTBridge *m_bridge;
  RCTModuleData *m_moduleData;
};

然后再ModuleRegistry的结构如下, 在初始化时, 会在内部持有modules_:

class RN_EXPORT ModuleRegistry {
 public:
  using ModuleNotFoundCallback = std::function<bool(const std::string &name)>;

  ModuleRegistry(
      std::vector<std::unique_ptr<NativeModule>> modules,
      ModuleNotFoundCallback callback = nullptr);
    
    ...

 private:
  // This is always populated
  std::vector<std::unique_ptr<NativeModule>> modules_;

  // This is only populated if moduleNames() is called.  Values are indices into
  // modules_.
  std::unordered_map<std::string, size_t> modulesByName_;

  ModuleNotFoundCallback moduleNotFoundCallback_;

  ...
};

2.3 react-cxx的真实setup方法 - Instance::initializeBridge(...)

// cxx类 Instance 的方法
void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback, // std::make_unique<RCTInstanceCallback>(self),
    std::shared_ptr<JSExecutorFactory> jsef, //executorFactory
    std::shared_ptr<MessageQueueThread> jsQueue, //_jsMessageThread
    std::shared_ptr<ModuleRegistry> moduleRegistry) { 
    
    // 1. 持有对外回调
    callback_ = std::move(callback);
    // 2. 持有所有的 NativeModule
    moduleRegistry_ = std::move(moduleRegistry);
    
    // 3. 核心: 同步执行, 在 JSThread 中实例化 NativeToJsBridge, 然后初始化 runtime, 启动 jsMessageQueue
    jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
        nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(jsef.get(), moduleRegistry_,  jsQueue, callback_);

        nativeToJsBridge_->initializeRuntime();

        /**
         * After NativeToJsBridge is created, the jsi::Runtime should exist.
         * Also, the JS message queue thread exists. So, it's safe to
         * schedule all queued up js Calls.
         */
        jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);

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

    CHECK(nativeToJsBridge_);
}

struct RCTInstanceCallback : public InstanceCallback {
  __weak RCTCxxBridge *bridge_;
    
  RCTInstanceCallback(RCTCxxBridge *bridge) : bridge_(bridge){};

  void onBatchComplete() override {
    // There's no interface to call this per partial batch
    [bridge_ partialBatchDidFlush];
    [bridge_ batchDidComplete];
  }
};

下面是一些关键逻辑的梳理:

  1. react-cxx中持有一个 callback_, 实际是为了JS中调用onBatchComplete时, 你能绑定找到bridge中的module的回调方法
  2. react-cxx中持外部已经完成封装的ModuleRegistry对象, 间接持有所有的JS能调用的NativeModule
  3. NativeToJsBridge的初始化和setup:
    1. JSThread同步执行,
    2. 实例化一个NativeToJsBridge
    3. JS Runtime
    4. 告知JS层, 可以启动MessageQueue
    5. 通知全局信号量!!! NativeToJs准备好啦!!!

因此, 以上逻辑的关键都指向 NativeToJsBridge 类型!!!