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);
}];
...
}
本文会梳理其中的几块内容:
- JS Thread 的创建, 以及开启 JS Thread 的 Runloop
- 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关联的逻辑主要包括两块:
JSThread的创建与启动: 直接在RCTCxxBridge start方法中创建JSThread启动入口函数: 在+runRunLoop作为JSThread的入口. 其中给 JSThread的Runloop启动, 并增加一个RunloopSource, 保活- 如何将block 调度到
JSThread中执行: 使用-ensureOnJavaScriptThread方法 JSThread中调度执行的block出现错误的分级处理: OC层错误, C++层错误...JSThread中任务执行错误时, 资源清理. 会用到_jsMessageThread
2. RN构造JS的初始执行环境
前面创建了OC层的JS线程(JSThread)并进行了保活处理. 下面会在这个JSThread中进行react cxx bridge初始化.
在理解代码之前, 需要先理解如下几个cxx类, 其中react 在cxx层的实现, 后面我们称为react-cxx:
std::shared_ptr<Instance> _reactInstance: 是react-cxx在cxx层的实例对象, 通过RCTCxxBridge持有, 大量的JS层调用方法都是通过它std::shared_ptr<JSExecutorFactory> executorFactory: JSExecutorFactory是 JSE 的工厂类型, 在Native 调用 JS时, 用来创建JSExcutorstd::shared_ptr<RCTMessageThread> _jsMessageThread: 是JSThread的cxx包装类, 方便cxx层的对象通过JSThread调度方法std::shared_ptr<ModuleRegistry>:react-cxx在cxx层中包装的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: JSThread包装.
_initializeBridge中, 包装成RCTMessageThread, 并赋值给RCTCxxBridge._jsMessageThread. - 准备工作2: 创建
ModuleRegistry._buildModuleRegistryUnlocked构造std::shared_ptr<ModuleRegistry> setupreact-cxx: 进行初始化时调用Instance::initializeBridge(...)
2.1 JSThread的cxx包装类RCTMessageThread
RN为了react-cxx在使用JSThread的方便, 会创建一个cxx对象_jsMessageThread, 它会持有JSThread.runloop, 然后对外提供类似dispatch_async,dispatch_sycn的方法runOnQueue和runOnQueueSync.
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, 实际我们的具体实现有两个类型:
RCTCxxModule: 没遇到过...RCTNativeModule: 这个就是我们常规的, 实现@protocol RCTModuleBridge的OCnative 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 &¶ms, int callId) override;
MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &¶ms) 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];
}
};
下面是一些关键逻辑的梳理:
- 在
react-cxx中持有一个callback_, 实际是为了JS中调用onBatchComplete时, 你能绑定找到bridge中的module的回调方法 - 在
react-cxx中持外部已经完成封装的ModuleRegistry对象, 间接持有所有的JS能调用的NativeModule NativeToJsBridge的初始化和setup:- 在
JSThread中同步执行, - 实例化一个
NativeToJsBridge JS Runtime- 告知
JS层, 可以启动MessageQueue啦 - 通知全局信号量!!!
NativeToJs准备好啦!!!
- 在
因此, 以上逻辑的关键都指向 NativeToJsBridge 类型!!!