为了进一步了解rn工作原理。近期在梳理rn项目启动流程。写篇文章记录一下。
梳理的过程。新建一个空白的rn项目。然后从iOS启动的代码深入进去,了解整个项目启动过程里都发生了什么事情。ps: 本文侧重于启动流程,未涉及到reload的流程。
代码版本:
"react": "16.13.1",
"react-native": "0.63.3"
整体流程简介
一. 创建bridge
核心方法: RCTCxxBridge 中的 Start
- 1、发送js将要loading的通知
- 2、创建js线程
- 3、加载原生模块
- 4、创建reactInstance实例
- 5、创建jsExecutor
- 6、创建nativeToJsBridge
- 7、加载js代码
- 8、执行js代码
二. 创建rootView
-
- 创建视图
-
- 添加监听
-
- 显示加载画面
-
- 处理js加载完毕后的逻辑
三. 添加到window上显示
结合代码梳理上述流程
首先我们看在AppDelegate.m
里,(对于不太了解iOS的同学,可以任务这里是项目的入口)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// 1.初始化bridge
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
// 2.创建rootView
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"rnNativeSourceCodeLearnDemo"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 3.创建跟控制器
UIViewController *rootViewController = [UIViewController new];
// 4.将rootViewController上面的视图替换为rootView
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
// 5.window显示
[self.window makeKeyAndVisible];
return YES;
}
从上述代码中可以看到。整个rn启动整体上看就做了两件事情:
- 1、创建bridge
- 2、创建rootView
先看第一个阶段
一、创建Bridge
从RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
开始,往里查看
RCTBridge.m
- (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions
{
return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions];
}
- (instancetype)initWithDelegate:(id)delegate
bundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions
{
if (self = [super init]) {
_delegate = delegate;
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
}
return self;
}
- (void)setUp
{
...
Class bridgeClass = self.bridgeClass;
...
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
...
}
最终核心的逻辑都是在RCTCxxBridge的start方法里,也就是文章一开始列举的8个步骤,下面是方法全部的内容:
// RCTCxxBridge.mm
- (void)start
{
...
// 1.发送通知:js将要开始loading
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge
userInfo:@{@"bridge" : self}];
// 2、创建jsThread,(用来负责执行js代码)
// Set up the JS thread early
_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();
...
// 3、加载原生模块
// 3.1、加载用户自定义的原生模块
[self registerExtraModules];
// Initialize all native modules that cannot be loaded lazily
// 3.2、加载官方定义的原生模块
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// 3.3、加载调试相关的原生模块
[self registerExtraLazyModules];
[_performanceLogger markStopForTag:RCTPLNativeModuleInit];
// 4、创建reactInstance实例。实际啥都没做,真实操作发生在后续的initializeBridge。不太清楚这里为啥这么写,跪请了解的大佬指点迷津。
// This doesn't really do anything. The real work happens in initializeBridge.
_reactInstance.reset(new Instance);
__weak RCTCxxBridge *weakSelf = self;
// 5、创建executorFactory对象。工厂模式的使用。内部会根据不同环境创建不同的executor。包括后期fb官方想切换executor,都很方便。
// Prepare executor factory (shared_ptr for copy into block)
std::shared_ptr executorFactory;
if (!self.executorClass) {
if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
id cxxDelegate = (id)self.delegate;
executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
}
if (!executorFactory) {
executorFactory = std::make_shared(nullptr);
}
} else {
id objcExecutor = [self moduleForClass:self.executorClass];
executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
}));
}
// 6、在JavaScript线程内异步执行初始化NativeToJsBridge的方法。native和js交互的实现基础,都是在此步骤内实现的
// Dispatch the instance initialization as soon as the initial module metadata has
// been collected (see initModules)
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 7、异步加载jsbundle代码
// Load the source asynchronously, then store it for later execution.
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()
id loadingView = [weakSelf moduleForName:@"DevLoadingView"
lazilyLoadIfNecessary:YES];
[loadingView updateProgress:progressData];
#endif
}];
// 8、 执行js代码。在6、7两个异步任务都完成后,才会触发该步骤执行
// Wait for both the modules and source code to have finished loading
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, @"");
}
接下来我们拆碎了看看每一步都做了些什么,探究一下rn这个框架的实现细节。
start方法内各个步骤的的细节
1.发送通知。
此处就是发送一个JavaScript将要开始加载的通知。
...
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge
userInfo:@{@"bridge" : self}];
...
2. 创建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];
...
创建一个js线程。后续js代码的执行都在这个线程里。除了js线程,后续还会出现一个js消息线程:jsMessageThread。这个线程处理的是native和js之间通信的。 runRunLoop的意义是线程保活。
整个rn框架里面存在三个线程:
- 主线程(UIThread)。负责渲染工作
- js线程(jsThread)。负责执行js代码。
- js消息线程(jsMessageThread)。负责管理所有的native和js之间的通讯。
+ (void)runRunLoop
{
@autoreleasepool {
...
// copy thread name to pthread name
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);
...
// run the run loop
while (kCFRunLoopRunStopped !=
CFRunLoopRunInMode(
kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
}
}
}
这个函数目的就是实现线程保活。如果开了子线程,不保活,执行完任务后,子线程就销毁了。
3、加载原生模块
该阶段内一共处理个三个事情。
- 1、加载用户自定义的原生模块
- 2、加载fb官方定义的原生模块
- 3、debug环境下加载debug相关的原生模块。比如LogBox
继续看一下每个加载过程都做了什么
3.1 加载用户自定义的原生模块
[self registerExtraModules];
// RCTCxxBridge.mm
- (void)registerExtraModules
{
...
// 获取所有的用户自定义的模块
NSArray> *extraModules = nil;
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
extraModules = [self.delegate extraModulesForBridge:_parentBridge];
} else if (self.moduleProvider) {
extraModules = self.moduleProvider();
}
...
// 遍历模块
for (id module in extraModules) {
Class moduleClass = [module class];
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
...
// 创建moduleData实例对象
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self];
// 用字典记录已经创建的结果,在后面阶段内,再次添加其他模块的时候,如果发现在此步骤已经有同名的,则不会再次创建。可以理解为我们可以重写某些官方定义的模块
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
// 这个数组记录所有的的模块信息
[_moduleDataByID addObject:moduleData];
}
...
}
1、获取所有用户自定义的模块信息
2、遍历模块,逐一创建对应的moduleData。
3、将创建的moduleData添加到_moduleDataByID数组内
看到此处可能有人会有疑问: 怎么获取到的需要加载哪些原生模块的呢?
这个问题,我们先暂且不管。就认为目前已经能拿到所有的需要导出的给js的原生模块。后续会详细讨论这个问题。
3.2 加载官方定义的原生模块
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
RCTCxxBridge.mm
- (NSArray *)_initializeModules:(NSArray *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
// 类似3.1中的处理过程,为导出的模块,创建对应的moduleData,添加到数组内。
// 最终返回moduleDataById数组。内部会记录每个模块是否需要到主线程初始化。
// 一般来讲涉及到UIKit相关的模块,以及需要创建静态变量的模块,都会需要到主线程实例化
NSArray *moduleDataById = [self _registerModulesForClasses:modules
lazilyDiscovered:lazilyDiscovered];
if (lazilyDiscovered) {
// 懒加载的模块此时不需要实例化对应的对象。但是貌似目前并没有需要懒加载的。
...
} else {
...
// 遍历上一步得到的moduleData列表
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
// 不需要到主线程的就在当前线程实例化
(void)[moduleData instance];
}
}
...
// From this point on, RCTDidInitializeModuleNotification notifications will
// be sent the first time a module is accessed.
_moduleSetupComplete = YES;
// 处理需要到主线程处理的模块
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
...
return moduleDataById;
}
- 1、类似3.1中的处理过程,为导出的模块创建对应的moduleData,添加到数组内。最终返回moduleDataById。内部会记录每个模块是否需要到主线程初始化。一般来讲涉及到UIKit相关的模块和需要创建静态变量的模块,都会需要到主线程实例化
- 2、遍历上一步的moduleDataById中的moduleData。如果不需要用到主线程的,就在当前实例化
- 3 最后处理要用到主线程的模块
看一下第一步的细节
- (NSArray *)_registerModulesForClasses:(NSArray *)moduleClasses
lazilyDiscovered:(BOOL)lazilyDiscovered
{
...
NSArray *moduleClassesCopy = [moduleClasses copy];
NSMutableArray *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
// 遍历所有的模块
for (Class moduleClass in moduleClassesCopy) {
if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
continue;
}
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
RCTModuleData *moduleData = _moduleDataByName[moduleName];
// 如果已经存在moduleData,就continue。
if (moduleData) {
if (moduleData.hasInstance || lazilyDiscovered) {
continue;
} else if ([moduleClass new] == nil) {
continue;
} else if ([moduleData.moduleClass new] != nil) {
RCTLogWarn(
@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass,
moduleName,
moduleData.moduleClass);
}
}
// 实例化还没有moduleData的模块,
moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self];
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
// 添加到本方法内部数组里
[moduleDataByID addObject:moduleData];
}
// 将此阶段所有的模块moduleData合并到总数组里
[_moduleDataByID addObjectsFromArray:moduleDataByID];
...
return moduleDataByID;
}
继续看一下第三步的方法,看一下具体怎么处理需要到主线程实例化的模块
- (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
...
BOOL initializeImmediately = NO;
if (dispatchGroup == NULL) {
...
initializeImmediately = YES;
}
...
// 遍历所有的moduleData
for (RCTModuleData *moduleData in _moduleDataByID) {
// 如果需要到主线程设置
if (moduleData.requiresMainQueueSetup) {
// 创建对应的block
dispatch_block_t block = ^{
if (self.valid && ![moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
...
(void)[moduleData instance];
[moduleData gatherConstants];
...
}
};
// 如果当前在主线程,立即执行对应的任务
if (initializeImmediately && RCTIsMainQueue()) {
block();
} else {
// 如果不是,切换到主线程执行block
if (dispatchGroup) {
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block);
}
}
_modulesInitializedOnMainQueue++;
}
}
...
}
- 1、遍历所有的moduleData
- 2、如果需要到主线程设置
- 3、创建实例化模块的block
- 4、 如果当前在主线程,立即执行对应的任务
- 5、 如果不是,切换到主线程执行block
4、 创建reactInstance实例
啥都没做。比较奇怪这里为什么会这么写。跪请了解的大佬指点迷津。
...
// 官方注释:此时啥都没做,实际的工作在initializeBridge里
_reactInstance.reset(new Instance);
...
5、 创建executorFactoty.
创建executorFactory对象。这里采用的工厂模式创建了一个执行器工厂,根据不同的环境可以最终创建不同的执行器实例。再生产环境下是JSIExecutor
.关于执行器暂且可以理解为这相当于js引擎,其实不对,里面虽然最终会对应一个引擎,额外的还有很多事情。只不过不耽误理解整体rn框架的逻辑。
...
__weak RCTCxxBridge *weakSelf = self;
// Prepare executor factory (shared_ptr for copy into block)
std::shared_ptr executorFactory;
if (!self.executorClass) {
if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
id cxxDelegate = (id)self.delegate;
executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
}
if (!executorFactory) {
executorFactory = std::make_shared(nullptr);
}
} else {
id objcExecutor = [self moduleForClass:self.executorClass];
executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
}));
}
...
6、 创建bridge.(最核心的部分)
这个部分一步步打通了native和js的通讯桥梁。使得native和js之间可以互相通讯。这地方能回答平时面试里可能遇到的native和js怎么通讯这个问题。
bridge的构建发生在JavaScript线程里。
此处强调一下。代码里多处出现bridge的说法。rn框架内也常常有人说通过bridge管理通讯。但是在代码里bridge就多个:
- RCTBridge。这个是最外层,是oc版本的bridge。内部包含RCTCxxBridge
- RCTCxxBridge。c++版本的bridge。内部包含NativeToJsBridge
- NativeToJsBridge。c++实现的。这个是管理原生端向js端通讯的bridge。内部直接可以获取到JSCRuntime(等同于引擎)。处理通讯核心逻辑的bridge。后面会做详细分析。
此处实例化bridge就是要构建所有的bridge,并打通native和js之间的通讯和交互。
...
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
// 上一步创建的executorFactory做参数
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
...
上一步创建的executorFactory做参数_initializeBridge
- (void)_initializeBridge:(std::shared_ptr)executorFactory
{
if (!self.valid) {
return;
}
// 在jsThread中创建JSMessageThread。并且把currentRunloop传递进去。
// 猜测是两个线程共用了一个runloop对象保活。能保证两个线程声明周期一致?
// 不太确定这么写法的目的。同样跪请有了解的大佬指点迷津
__weak RCTCxxBridge *weakSelf = self;
_jsMessageThread = std::make_shared([NSRunLoop currentRunLoop], ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
});
...
// This can only be false if the bridge was invalidated before startup completed
if (_reactInstance) {
...
// 初始化bridge
[self _initializeBridgeLocked:executorFactory];
...
}
...
}
在jsThread中创建JSMessageThread。并且把currentRunloop传递进去。 猜测是两个线程共用了一个runloop对象保活。能保证两个线程声明周期一致? 不太确定这么写法的目的。同样跪请有了解的大佬指点迷津。
然后走到[self _initializeBridgeLocked:executorFactory];
方法。字面意思就能看明白是需要通过加锁的方式初始化bridge,确保线程安全
- (void)_initializeBridgeLocked:(std::shared_ptr)executorFactory
{
// 加锁确保线程安全
std::lock_guard guard(_moduleRegistryLock);
// This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
_reactInstance->initializeBridge(
std::make_unique(self),
executorFactory,
_jsMessageThread,
[self _buildModuleRegistryUnlocked]);
_moduleRegistryCreated = YES;
}
介绍一下_reactInstance->initializeBridge
方法的参数:
- 初始化bridge成功后的回调
- executorFactory。一开始创建的执行器工厂对象
- jsMessageThread。这个是js消息线程。管理native和js之间通讯的
- 原生模块的注册后的信息。这里并不是直接将模块信息传递进去,而是将之前加载的所有原生模块,做了一层包装。这个新对象内部能获取到所有原生模块的信息。
看一下是怎么注册原生模块的
- (std::shared_ptr)_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())];
};
// 调用createNativeModules,获取原生模块注册管理对象registry
auto registry = std::make_shared(
createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback);
...
// 返回原生模块注册管理对象registry
return registry;
}
createNativeModules(NSArray *modules, RCTBridge *bridge, const std::shared_ptr &instance)
{
// 创建nativeModules对象
std::vector> nativeModules;
// 遍历所有的moduleData
for (RCTModuleData *moduleData in modules) {
// 如果是RCTCxxModule类的子类
if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
// 包装成在添加进去CxxNativeModule
nativeModules.emplace_back(std::make_unique(
instance,
[moduleData.name UTF8String],
[moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },
std::make_shared(moduleData)));
} else {
// // 如果不是RCTCxxModule类的子类,包装成RCTNativeModule添加进去
nativeModules.emplace_back(std::make_unique(bridge, moduleData));
}
}
// 最终返回原生模块注册表
return nativeModules;
}
这里整体概况一下,就是将之前生成的原生模块的配置信息添加到一个注册表里。然后把这个配置表对象,作为下一步的参数。
我们可以看到native模块是分为c++模块和oc模块两类的
最后看一下CxxNativeModule和RCTNativeModule的结构
class RN_EXPORT CxxNativeModule : public NativeModule {
public:
CxxNativeModule(
std::weak_ptr instance,
std::string name,
xplat::module::CxxModule::Provider provider,
std::shared_ptr messageQueueThread)
: instance_(instance),
name_(std::move(name)),
provider_(provider),
messageQueueThread_(messageQueueThread) {}
std::string getName() override;
std::vector getMethods() override;
folly::dynamic getConstants() override;
void invoke(unsigned int reactMethodId, folly::dynamic &¶ms, int callId)
override;
MethodCallResult callSerializableNativeHook(
unsigned int hookId,
folly::dynamic &&args) override;
private:
void lazyInit();
std::weak_ptr instance_;
std::string name_;
xplat::module::CxxModule::Provider provider_;
std::shared_ptr messageQueueThread_;
std::unique_ptr module_;
std::vector methods_;
};
// ------------------------------------我是一条分割线---------------------------------------------
class RCTNativeModule : public NativeModule {
public:
RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData);
std::string getName() override;
std::vector 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;
};
都会有这么几个方法
获取模块名:
std::string getName() override
:获取模块里的方法列表
std::vector
:getMethods() override 获取模块里的常量
folly::dynamic getConstants() override;
:执行某函数
void invoke(unsigned int reactMethodId, folly::dynamic &¶ms, int callId) override;
:序列化
MethodCallResult callSerializableNativeHook( unsigned int hookId, folly::dynamic &&args) override;
:
这里先看到这里我们回过头来继续看bridge的初始化
- 成功后的回调
- executorFactory。执行器工厂对象
- jsMessageThread。js消息线程。
- 原生模块的注册后的信息。
- (void)_initializeBridgeLocked:(std::shared_ptr)executorFactory
{
std::lock_guard guard(_moduleRegistryLock);
// This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
_reactInstance->initializeBridge(
std::make_unique(self),
executorFactory,
_jsMessageThread,
[self _buildModuleRegistryUnlocked]);
_moduleRegistryCreated = YES;
}
initializeBridge
在Instance.cpp
中,是个c++的方法
// Instance.cpp
void Instance::initializeBridge(
std::unique_ptr callback,
std::shared_ptr jsef,
std::shared_ptr jsQueue,
std::shared_ptr moduleRegistry) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
// 往jsQueue里添加一个任务。此处的jsQueue对应的线程是外部传进来的jsMessageThread。不是jsThread。
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
// 任务的内容
// 1、创建nativeToJsBridge_实例
nativeToJsBridge_ = std::make_shared(
jsef.get(), moduleRegistry_, jsQueue, callback_);
// 2、调用nativeToJsBridge_实例中初始化runtime的方法
nativeToJsBridge_->initializeRuntime();
// 3、将刚创建的nativeToJsBridge_设置给m_nativeToJsBridge记录下来。
// 然后清空对js的调用的任务队列,清空是指取出来执行完。
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::lock_guard lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
...
}
这个方法的本质是往js消息队列里添加了一个任务。后续在执行队列任务的时候,再取出来执行。
我们看一下这个任务的内容:
- 1、创建
nativeToJsBridge_
实例 - 2、调用
nativeToJsBridge_
实例中初始化runtime的方法 - 3、将刚创建的
nativeToJsBridge_
设置给m_nativeToJsBridge
记录下来。然后清空对js的调用的任务队列,清空是指取出来执行完。
本身是一个要入队的任务。这个任务里,最后还会触发一次取出来任务继续执行的操作。猜测一下:应该是因为在实际执行initializeRuntime是,可能队列里又加入别的任务,也要执行这些任务。
继续跟下去。看看初始化runtime做了啥:
// NativeToJsBridge.cpp
void NativeToJsBridge::initializeRuntime() {
// 往执行队列里添加一个任务
runOnExecutorQueue(
// 任务本身是调用executor的初始化runtime
[](JSExecutor *executor) mutable { executor->initializeRuntime(); });
}
这个方法本质也是创建一个任务,添加到执行队列里。
任务的内容是调用executor的初始化runtime方法
继续跟踪,看一下最终的初始化runtime发生了什么。 在JSIExecutor.cpp
中
// JSIExecutor.cpp
void JSIExecutor::initializeRuntime() {
SystraceSection s("JSIExecutor::initializeRuntime");
// 1、在jscruntime中获取全局对象,设置一个nativeModuleProxy属性,value是一个NativeModuleProxy类型的对象
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared(nativeModules_)));
// 2、在jscruntime中获取全局对象,设置一个nativeFlushQueueImmediate属性,value是一个createFromHostFunction函数
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) {
if (count != 1) {
throw std::invalid_argument(
"nativeFlushQueueImmediate arg count must be 1");
}
callNativeModules(args[0], false);
return Value::undefined();
}));
// 3、在jscruntime中获取全局对象,设置一个nativeCallSyncHook属性,value是一个createFromHostFunction函数
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) { return nativeCallSyncHook(args, count); }));
...
if (runtimeInstaller_) {
runtimeInstaller_(*runtime_);
}
...
}
敲黑板,全局核心。
native端获取到runtime实例。可以认为是js的执行环境。在这个环境的global对象上,添加了几个属性并赋值。这个global对象,在js的执行上下文中可以直接获取到。就是js里的global对象。global上的属性也能直接在js端获取到,并且直接使用。
这个地方是native和js之间直接交互的部分,也是native和js可以互相通讯的核心所在。
整个bridge的构建核心就是完成这个global对象的构建。
整个global对象类似下面的结构
global = {
nativeModuleProxy: NativeModuleProxy,
nativeFlushQueueImmediate: (flushQueue) => { callNativeModules(flushQueue) },
nativeCallSyncHook: (args) => {return nativeCallSyncHook(args, count)}
}
概括的介绍一下这几个属性:
- nativeModuleProxy: 简单来说是原生模块的配置信息。本质上是对原生模块配置表的一个包装。内部可以通过moduleID,methodID可以查找到对应的原生模块和模块方法。
- nativeFlushQueueImmediate: 这个是一个函数,可以认为是js端负责调用。native负责实现。这个是js端唯二主动调用native的其中一个地方。
- nativeCallSyncHook: 本质是一个函数。js端负责调用,native端负责实现。当js端发起一个对原生模块的同步调用时,会在js端调用此方法。到达native端后,方法内会根据传递过来的moduleID和methodID查找到对应的原生模块的原生方法,执行完毕后,将结果返回给js端。这是另一个js端会直接调用native的地方。
自此native和js通讯的bridge基本构造完毕。实际native和js端能通信的的就是上述这个global对象。
为啥说是基本构造完毕,因为除了native端给global设置属性外,js端也会给这个global设置属性。后文会提到,此处先做个简介
global = {
nativeModuleProxy: NativeModuleProxy,
nativeFlushQueueImmediate: (flushQueue) => { callNativeModules(flushQueue) },
nativeCallSyncHook: (args) => {return nativeCallSyncHook(args, count)},
__fbBatchedBridge: BatchedBridge, // js端添加的
__fbGenNativeModule: function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{
name: string,
module?: Object,
...
}, // js添加
}
为了加深印象和便于理解,详细介绍一下这几个属性
- NativeModuleProxy: 负责管理所有的原生模块的注册信息。到了js端会对应rn开发中使用的NativeModules。是同一个对象。
当在js端使用原生模块的时候:
import {NativeModules} from 'react-native';
const CalendarManager = NativeModules.CalendarManager;
export default CalendarManager;
NativeModules.CalendarManager
,会调用到原生的NativeModuleProxy实例里的get方法
// JSIExecutor.app
Value get(Runtime &rt, const PropNameID &name) override {
if (name.utf8(rt) == "name") {
return jsi::String::createFromAscii(rt, "NativeModules");
}
// 获取原生模块注册信息
auto nativeModules = weakNativeModules_.lock();
// 如果不存在返回空
if (!nativeModules) {
return nullptr;
}
// 如果存在返回getModule执行后的结果
return nativeModules->getModule(rt, name);
}
NativeModuleProxy会根据传递过来的moduleName获取native端对应的模块注册信息,并返回给js端。
继续看看getModule做了什么
// JSINativeModules.cpp
Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name) {
// 如果原生模块的注册表对象不存在,返回空
if (!m_moduleRegistry) {
return nullptr;
}
// 获取要获取的模块名称
std::string moduleName = name.utf8(rt);
// 查询缓存,如果缓存里找到了,且不是最后一个,返回当前对象的second。
const auto it = m_objects.find(moduleName);
if (it != m_objects.end()) {
return Value(rt, it->second);
}
// 如果缓存里没有找到,则创建module
auto module = createModule(rt, moduleName);
// 如果创建的模块是没有信息的,返回空
if (!module.hasValue()) {
// Allow lookup to continue in the objects own properties, which allows for
// overrides of NativeModules
return nullptr;
}
// 添加到缓存里,返回当前的second。具体为啥这么设计,不太明白。跪求了解的大佬指点
auto result =
m_objects.emplace(std::move(moduleName), std::move(*module)).first;
return Value(rt, result->second);
}
- 1、查看原生模块注册表存不存在,不存在直接返回空
- 2、 根据moduleName获取对应的模块缓存信息,如果有缓存直接序列化一下返回给js端
- 3、 如果没有缓存则创建一份模块数据
- 4、 创建的数据如果没有值,也返回空
- 5、将新创建的模块信息添加到缓存里
- 6、将数据序列化以后返回给js端
在上述过程中,可以看到有个createModule的操作。
继续看看createModule
里发生了什么
folly::Optional
先梳理一下过程
- 1、先看一下
m_genNativeModuleJS
这个函数存不存在 - 2、如果不存在需要去global对象去获取一下实现。这个函数的实现是在js端。
- 3、同样先查一遍缓存
- 4、如果查到了直接返回
- 5、如果没有查到就调用
m_genNativeModuleJS
这个js端实现的函数创建对应的模块信息 - 6、将模块信息添加到缓存
- 7、 返回模块信息
此处有个m_genNativeModuleJS
函数。
我们继续跟,看看m_genNativeModuleJS函数在js端的实现是怎么做的
// NativeModule.js
export type ModuleConfig = [
string /* name */,
?Object /* constants */,
?$ReadOnlyArray /* functions */,
?$ReadOnlyArray /* promise method IDs */,
?$ReadOnlyArray /* sync method IDs */,
];
export type MethodType = 'async' | 'promise' | 'sync';
// 一、函数声明
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{
name: string,
module?: Object,
...
} {
if (!config) {
return null;
}
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
...
if (!constants && !methods) {
// Module contents will be filled in lazily later
return {name: moduleName};
}
const module = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
promiseMethods && arrayContains(promiseMethods, methodID);
const isSync = syncMethods && arrayContains(syncMethods, methodID);
...
// 遍历每个方法,确定每个方法的类型。同步、异步、promise
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
// 生成对应的js函数,然后存到module内
module[methodName] = genMethod(moduleID, methodID, methodType);
});
// 将静态变量合并到module里
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
} else {
console.warn(
`Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
);
}
...
// 最终在js端创建完js端的函数和静态变量以后,把信息返回给native端
return {name: moduleName, module};
}
// export this method as a global so we can call it from native
// 二、赋值给global对象的__fbGenNativeModule属性。导出此方法,让native端可以拿到
global.__fbGenNativeModule = genModule;
这段代码概括来讲就干了俩事。
- 1、声明一个
genModule
函数。在这个函数里创建对应的native模块信息,这个信息不会直接在js端使用。而是先返回给native端。在native端处理以后使用。 - 2、把这个函数赋值给global对象上的
__fbGenNativeModule
属性。给native端调用。
梳理一下genModule
是怎么做的
- 1、获取模块配置信息
- 2、如果没有方法也没有静态变量,直接返回
- 3、遍历方法列表,逐个确认每个方法的类型:promise、同步、异步三种。
- 4、不同的方法创建不同的函数然后添加到module内
- 5、将静态变量合并到module内
- 6、返回module给原生
看看genMethod
这个函数。在js端是怎么给模块创建函数的
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
// 如果是promise类型,最终返回一个函数,这个函数返回值是promise类型的对象
if (type === 'promise') {
fn = function promiseMethodWrapper(...args: Array) {
// In case we reject, capture a useful stack trace here.
const enqueueingFrameError: ExtendedError = new Error();
return new Promise((resolve, reject) => {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData =>
reject(updateErrorWithErrorData(errorData, enqueueingFrameError)),
);
});
};
} else {
// 如果不是promise类型
fn = function nonPromiseMethodWrapper(...args: Array) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
const hasErrorCallback = typeof secondLastArg === 'function';
hasErrorCallback &&
invariant(
hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
const onSuccess = hasSuccessCallback ? lastArg : null;
const onFail = hasErrorCallback ? secondLastArg : null;
const callbackCount = hasSuccessCallback + hasErrorCallback;
args = args.slice(0, args.length - callbackCount);
// 如果是同步函数。会调用原生端提供的callNativeSyncHook方法,
// 通过原生端去生成对应的函数实现,然后把返回值拿来以后,最终存到module对象里。
if (type === 'sync') {
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
} else {
// 如果是异步函数,直接加入到js端的消息队列里。
// 这是一个js向native端通讯的队列,队列里的内容都是异步执行的
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
}
};
}
fn.type = type;
return fn;
}
- 0、 创建一个fn对象
- 1、如果是promise的返回一个函数,创建一个返回值是promise对象的函数赋值给fn
- 2、 如果是同步函数,创建一个普通函数赋值给fn。这个普通函数的返回值是调用
BatchedBridge.callNativeSyncHook
的执行结果。 - 3、 如果是异步函数。将没有返回值的普通函数赋值给fn。并在jsToNative的任务队列里,添加一个任务
- 4、最终设置一下fn变量的函数的类型,返回把fn对象返回。
BatchedBridge.callNativeSyncHook
最终是js端调用的一个函数global.nativeCallSyncHook
,此函数js端负责调用,native端负责实现。js发起调用的时候可以直接找到对应的原生模块和方法,同步执行。
继续往下看两个地方:第一个是callNativeSyncHook
的实现和enqueueNativeCall
的实现
先看第一个callNativeSyncHook
// MessageQueue.js
callNativeSyncHook(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
): any {
...
// 创建对应的回调,并记录一下
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
// 最终调用是native端的nativeCallSyncHook的实现
return global.nativeCallSyncHook(moduleID, methodID, params);
}
最终调用的是global.nativeCallSyncHook(moduleID, methodID, params)
。 这个函数 js端负责调用。native端负责实现。
看一下native端对nativeCallSyncHook
的实现
// JSIExecutor.cpp
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) { return nativeCallSyncHook(args, count); }));
可以看到native端调用的是nativeCallSyncHook
// JSIExecutor.cpp
Value JSIExecutor::nativeCallSyncHook(const Value *args, size_t count) {
if (count != 3) {
throw std::invalid_argument("nativeCallSyncHook arg count must be 3");
}
if (!args[2].asObject(*runtime_).isArray(*runtime_)) {
throw std::invalid_argument(
folly::to("method parameters should be array"));
}
MethodCallResult result = delegate_->callSerializableNativeHook(
*this,
static_cast(args[0].getNumber()), // moduleId
static_cast(args[1].getNumber()), // methodId
dynamicFromValue(*runtime_, args[2])); // args
if (!result.hasValue()) {
return Value::undefined();
}
return valueFromDynamic(*runtime_, result.value());
}
// NativeToJsBridge.cpp
MethodCallResult callSerializableNativeHook(
__unused JSExecutor &executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic &&args) override {
return m_registry->callSerializableNativeHook(
moduleId, methodId, std::move(args));
}
可以看到原生端是直接通过moduleID,methodID和args调用了对应模块的对应函数,并把返回值返回给js端。
由此可以看到,rn框架里,js是可以同步调用native的。上述就是js同步执行native方法,然后把返回值返回给js端的实现
我们再看看异步调用的流程enqueueNativeCall
enqueueNativeCall(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
) {
// 生成对应的回调函数并记录下来
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
// 将当前的这次调用添加到消息队列里
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
...
this._queue[PARAMS].push(params);
// 获取当前的时间戳
const now = Date.now();
// 如果有nativeFlushQueueImmediate且本次调用距离上一次清空队列的时间大于5ms。
// 则通过nativeFlushQueueImmediate主动把当前消息队列发给native端去执行。
// 默认情况下是native端主动向js端取数据然后执行。只有大于5ms还没有取的时候,js会主动发起执行
if (
global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
) {
const queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
// 主动将队列数据,发送给native端调用
global.nativeFlushQueueImmediate(queue);
}
...
}
我们可以看到大部分入队操作都是紧紧把任务入队就结束了。等待native端来取出来队列然后执行。但是当native端超过5ms没有取过数据了。就会主动调用nativeFlushQueueImmediate
方法,将任务队列push到native去执行。这个方法也是js端调用,native负责实现
native端对nativeFlushQueueImmediate
的实现:
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) {
if (count != 1) {
throw std::invalid_argument(
"nativeFlushQueueImmediate arg count must be 1");
}
callNativeModules(args[0], false);
return Value::undefined();
}));
最终执行到native端的callNativeModules
整个初始化runtime的参数介绍完了。基本上js端对native端的调用流程也能整理出来了。
我们通过一个例子来总结一下js端调用native端的整个流程
// native端
// CalendarManager.m
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
// ---------------------------
// js端
import {NativeModules} from 'react-native';
const CalendarManager = NativeModules.CalendarManager;
export default CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
- 1、
NativeModules
对应NativeModulesProxy
- 2、
NativeModules.CalendarManager
等价于调用NativeModulesProxy
中的get(“CalendarManager”)
方法. - 3、 然后
NativeModulesProxy
先去内部缓存里查。 - 4、如果查到了直接返回对应的模块信息。此时假定没有缓存
- 5、如果没有查到就内部原生模块注册类的
nativeModules->getModule(“CalendarManager”)
方法 - 6、进来以后也是查缓存。假定也没有缓存。
- 7、 继续调用
module = createModule(“CalendarManager”)
函数 - 8、然后判断
m_genNativeModuleJS
这个函数存不存在,如果不存在,则去global对象上取值 - 9、然后再次查缓存,如果存在就返回。我们假定仍没有缓存
- 10、 native调用js端实现的
m_genNativeModuleJS
函数。让js生成对应的模块信息 - 11、来到js端执行
genModule(CalendarManagerConfig,"CalendarManagerId")
这个函数 - 12、 从config中获取方法列表。
- 13、 遍历方法列表
- 14、 判断每个方法的类型 是promise 同步 异步。
- 15、分别创建js端的函数。
- 16、然后把创建的函数放到module对象里
- 17、把module返回给native端
- 18、 此时回到native端。将js端返回过来的module信息存到缓存里,然后返回给js端。
- 19、 js端获取到CalendarManager模块信息。对应的模块在js端也有了对应的模块映射信息等内容。直接调用
addEvent
方法 - 20、 这个方法是个异步方法。最终会添加加到JSToNative的消息队列里。
- 20、假定如果上一次native端取队列的值超过了5ms。
- 21、js端调用
global.nativeFlushQueueImmediate(queue)
主动调用native - 22、 native端执行
callNativeModules("CalendarManager", "addEvent")
. - 23、 然后native端执行CalendarManager模块的addEvent方法。
- 24、 最终控制台输出内容。
整个调用流程结束
7、 加载jsbundle代码
此部分逻辑和上一部分初始化bridge 是同时异步执行的。
此部分相对比较简单,就是加载一下指定路径的bundle文件。开发环境下显示加载进度。加载成功后通知可以执行下一步。
...
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()
id loadingView = [weakSelf moduleForName:@"DevLoadingView"
lazilyLoadIfNecessary:YES];
// 加载过程中,更新加载进度条
[loadingView updateProgress:progressData];
#endif
}];
...
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
// 发送通知 RCTBridgeWillDownloadScriptNotification 将要下载script
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge];
[_performanceLogger markStartForTag:RCTPLScriptDownload];
NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil);
// Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out
(void)cookie;
RCTPerformanceLogger *performanceLogger = _performanceLogger;
// 创建load成功的回调
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) {
...
NSDictionary *userInfo = @{
RCTBridgeDidDownloadScriptNotificationSourceKey : source ?: [NSNull null],
RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey : self->_bridgeDescription ?: [NSNull null],
};
// 发送通知RCTBridgeDidDownloadScriptNotification
[center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo];
_onSourceLoad(error, source);
};
// 开始load
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) {
NSError *error = RCTErrorWithMessage(
@"No bundle URL present.\n\nMake sure you're running a packager "
"server or have included a .jsbundle file in your application bundle.");
onSourceLoad(error, nil);
} else {
// 开发环境下,新项目执行此处
__weak RCTCxxBridge *weakSelf = self;
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL
onProgress:onProgress
onComplete:^(NSError *error, RCTSource *source) {
if (error) {
[weakSelf handleError:error];
return;
}
onSourceLoad(error, source);
}];
}
}
// RCTJavaScriptLoader.mm
+ (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;
}
const BOOL isCannotLoadSyncError = [error.domain isEqualToString:RCTJavaScriptLoaderErrorDomain] &&
error.code == RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously;
// 再尝试异步加载,成功返回,失败的话调用回调返回失败
if (isCannotLoadSyncError) {
attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
} else {
onComplete(error, nil);
}
}
我们看一下加载成功后要做啥
8、 执行js代码
在6、7都执行完毕后,才会开始执行本步骤。
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];
}
});
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
// 创建js执行成功的回调
dispatch_block_t completion = ^{
// Log start up metrics early before processing any other js calls
[self logStartupFinish];
// 处理pending中的native对js端的调用任务
[self _flushPendingCalls];
// 到主线程上发送RCTJavaScriptDidLoadNotification的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self->_parentBridge
userInfo:@{@"bridge" : self}];
// Starting the display link is not critical to startup, so do it last
[self ensureOnJavaScriptThread:^{
// 将js的调用和渲染帧关联到一起。
[self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
}];
});
};
// 根据参数,同步或者异步 开始执行script
if (sync) {
[self executeApplicationScriptSync:sourceCode url:self.bundleURL];
completion();
} else {
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
}
...
}
整体概括一下就是两步
- 1、创建一个执行js代码执行成功后的回调
- 2、根据参数去确定同步执行还是异步执行js代码
不管同步还是异步,最终执行的方法都是同一个方法
- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async
{
[self _tryAndHandleError:^{
NSString *sourceUrlStr = deriveSourceURL(url);
// 发送RCTJavaScriptWillStartExecutingNotification的通知
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartExecutingNotification
object:self->_parentBridge
userInfo:@{@"bridge" : self}];
auto reactInstance = self->_reactInstance;
// 如果是RAM中的script就走这里
if (isRAMBundle(script)) {
...
auto ramBundle = std::make_unique(sourceUrlStr.UTF8String);
std::unique_ptr scriptStr = ramBundle->getStartupCode();
...
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(script), sourceUrlStr.UTF8String, !async);
} else {
std::string methodName = async ? "loadBundle" : "loadBundleSync";
throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
}
}];
}
因为整个流程是本地初始化一个空项目然后再开发环境下运行的。所以本文案例代码会从reactInstance->loadScriptFromString
执行
// Instance.cpp
void Instance::loadScriptFromString(
std::unique_ptr string,
std::string sourceURL,
bool loadSynchronously) {
...
if (loadSynchronously) {
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadBundle(nullptr, std::move(string), std::move(sourceURL));
}
}
同样的最终都是同一个方法loadBundle
void Instance::loadBundle(
std::unique_ptr bundleRegistry,
std::unique_ptr string,
std::string sourceURL) {
// 对js的调用+1
callback_->incrementPendingJSCalls();
...
nativeToJsBridge_->loadBundle(
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}
// NativeToJsBridge.cpp
void NativeToJsBridge::loadBundle(
std::unique_ptr bundleRegistry,
std::unique_ptr startupScript,
std::string startupScriptSourceURL) {
// 入队
runOnExecutorQueue(
[this,
bundleRegistryWrap = folly::makeMoveWrapper(std::move(bundleRegistry)),
startupScript = folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL =
std::move(startupScriptSourceURL)](JSExecutor *executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
executor->loadBundle(
std::move(*startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
执行到NativeToJsBridge里。可以看到,此处本质上就是创建个任务然后入队。
看一下任务内容executor->loadBundle(std::move(*startupScript), std::move(startupScriptSourceURL));
void JSIExecutor::loadBundle(
std::unique_ptr script,
std::string sourceURL) {
SystraceSection s("JSIExecutor::loadBundle");
...
runtime_->evaluateJavaScript(
std::make_unique(std::move(script)), sourceURL);
flush();
...
}
最终的终点是到了jsruntime这里。
整个内容也是两步
- 1、
runtime_->evaluateJavaScript
runtime_
:可以认为是js引擎。runtime_->evaluateJavaScript
此处就到了引擎执行代码阶段。 - 2、 执行
flush()
函数
// JSIExecutor.cpp
void JSIExecutor::flush() {
...
// flushedQueue_ 记录着js端对native端的调用任务。
if (flushedQueue_) {
// 执行完js后,如果此时js端发生了对native的调用。此处立即执行一下。
callNativeModules(flushedQueue_->call(*runtime_), true);
return;
}
// 从runtime上的global对象是获取batchedBridge。如果js端发生了对native的调用,此时这个bridge是肯定会有值的。
Value batchedBridge =
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
// 如果此时是undefined,说明js端没有主动调用native模块,所以没有赋值
if (!batchedBridge.isUndefined()) {
// 此时native端,主动发起一起bindBridge,将native和js连接到一起
bindBridge();
// 然后再调用native模块
callNativeModules(flushedQueue_->call(*runtime_), true);
} else if (delegate_) {
callNativeModules(nullptr, true);
}
}
flushedQueue_
整个队列记录着js对native端的调用任务。在js初始化执行的过程中。有可能产生了js对native的调用。初始化js后,native端要先把这些调用执行完。避免遗漏一些初始化操作。
batchedBridge
:如果js发生了对native的调用,此时这个对象是存在的。如果没有发生对native的调用。native端主动发起一起BindBridge。这样确保js和native的通道是畅通的。便于后面的交互。
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");
});
}
还记得之前初始化runtime的时候,js端和native都往global对象上添加了一些属性。基本上都是用来处理jsToNative调用逻辑的
bindBridge函数内也是往这个global对象上添加了属性。此次添加的基本上都是nativeToJs的调用相关的内容
callFunctionReturnFlushedQueue
: 调用js端的方法,然后返回flushqueue(里面记录jsToNative的调用)
invokeCallbackAndReturnFlushedQueue
: 执行js端记录的回调,然后返回flushqueue
flushedQueue
: flushqueue对象
看一下每个函数的实现
callFunctionReturnFlushedQueue(
module: string, // 模块名称id
method: string, // 方法名称id
args: any[], // 参数
): null | [Array, Array, Array, number] {
// 执行函数
this.__guard(() => {
this.__callFunction(module, method, args);
});
// 返回队列
return this.flushedQueue();
}
__callFunction(module: string, method: string, args: any[]): void {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
...
// 获取模块方法
const moduleMethods = this.getCallableModule(module);
...
// 根据方法id取出来对应的方法执行
moduleMethods[method].apply(moduleMethods, args);
...
}
flushedQueue(): null | [Array, Array, Array, number] {
this.__guard(() => {
this.__callImmediates();
});
// 将当期队列信息返回给native端,并清空js端队列
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
callFunctionReturnFlushedQueue
: 做的事情是native端发起对js端的调用,在js端查找对应的模块和方法,执行之后,在将当前的flushqueue队列信息返回给native端
invokeCallbackAndReturnFlushedQueue(
cbID: number,
args: any[],
): null | [Array, Array, Array, number] {
// 根据callbackid执行对应的回调
this.__guard(() => {
this.__invokeCallback(cbID, args);
});
// 返回flushqueue队列的数据
return this.flushedQueue();
}
__invokeCallback(cbID: number, args: any[]) {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
// 查询callbackid,取出来callback函数
const callID = cbID >>> 1;
const isSuccess = cbID & 1;
const callback = isSuccess
? this._successCallbacks.get(callID)
: this._failureCallbacks.get(callID);
...
// 如果没有callback直接返回
if (!callback) {
return;
}
// 删除对应的callback
this._successCallbacks.delete(callID);
this._failureCallbacks.delete(callID);
// 执行callback
callback(...args);
...
}
invokeCallbackAndReturnFlushedQueue
:这个方法就是调用回调然后返回flushqueue队列的数据。
flushedQueue
: 未找到实际处理的代码,大胆猜测是对应js端的flushedQueue函数。获取flushqueue队列数据。
关于JSCRuntime中的global对象我们再整理一下:
global = {
nativeModuleProxy: NativeModuleProxy,
nativeFlushQueueImmediate: (flushQueue) => { callNativeModules(flushQueue) },
nativeCallSyncHook: (args) => {return nativeCallSyncHook(args, count)},
__fbBatchedBridge: BatchedBridge, // js端添加的
__fbGenNativeModule:(moduleCondig, moduleId)=> {moduleName, module} ,
callFunctionReturnFlushedQueue: (module,method, args) => flushQueue,
invokeCallbackAndReturnFlushedQueue:(cbId, args) => flushQueue,
flushedQueue: () => flushQueue,
}
nativeModuleProxy
: 记录原生模块注册信息,
nativeFlushQueueImmediate
: js负责调用,native负责实现。
nativeCallSyncHook
: js负责调用,native负责实现。
__fbBatchedBridge
: jsToNative的调用的消息队列const BatchedBridge: MessageQueue = new MessageQueue();
__fbGenNativeModule
: native端调用,js负责实现。
callFunctionReturnFlushedQueue
: native端调用,js端负责实现。
invokeCallbackAndReturnFlushedQueue
: native端调用,js端负责实现。
flushedQueue
: native端调用。js负责实现。
梳理一下整体逻辑
在构造玩bridge和加载完jsbundle代码以后。开始执行js代码。
先创建个执行成功后的回调,
然后开始执行js。
对js的执行本质,是往js消息线程的任务队列里添加一个任务
任务的内容是让jsctime执行js代码,然后执行完以后还要立即处理一下在这个期间产生的js对native的调用。
然后一开始创建的执行成功的回调作为下一个任务执行。
自此,初始化的js代码执行完毕。
二. 创建rootView
-
- 创建视图
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
{
...
if (self = [super initWithFrame:CGRectZero]) {
self.backgroundColor = [UIColor whiteColor];
_bridge = bridge;
_moduleName = moduleName;
_appProperties = [initialProperties copy];
_loadingViewFadeDelay = 0.25;
_loadingViewFadeDuration = 0.25;
_sizeFlexibility = RCTRootViewSizeFlexibilityNone;
_minimumSize = CGSizeZero;
// 添加监听
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidReload)
name:RCTJavaScriptWillStartLoadingNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
...
// 显示loading
[self showLoadingView];
// Immediately schedule the application to be started.
// (Sometimes actual `_bridge` is already batched bridge here.)
// 4. 处理加载完毕后的逻辑
[self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
}
...
return self;
}
-
- 添加监听
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidReload)
name:RCTJavaScriptWillStartLoadingNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
-
- 显示加载画面
[self showLoadingView];
-
- 处理js加载完毕后的逻辑
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
...
if (!bridge.valid) {
return;
}
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge
reactTag:self.reactTag
sizeFlexiblity:_sizeFlexibility];
[self runApplication:bridge];
_contentView.passThroughTouches = _passThroughTouches;
[self insertSubview:_contentView atIndex:0];
if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
self.intrinsicContentSize = self.bounds.size;
}
}
- (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方法,是做了一个入队的操作。 发起对js端AppRegistry模块runApplication方法的调用。可以理解为这个方法是js端的启动入口方法。
// RCTBridge.mm
- (void)enqueueJSCall:(NSString *)module
method:(NSString *)method
args:(NSArray *)args
completion:(dispatch_block_t)completion
{
if (!self.valid) {
return;
}
...
__weak __typeof(self) weakSelf = self;
// 确保在加载完成以后执行
[self _runAfterLoad:^() {
...
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// 主要方法,内部主要逻辑是把对js的调用,添加到_jsMessageThread的执行队列里
if (strongSelf->_reactInstance) {
strongSelf->_reactInstance->callJSFunction(
[module UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[]));
// ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
// the block is invoked after callJSFunction
// 执行完毕的回调调价到_jsMessageThread的执行队列里,会在上一步所有任务执行完后,最后执行回调
if (completion) {
if (strongSelf->_jsMessageThread) {
strongSelf->_jsMessageThread->runOnQueue(completion);
} else {
RCTLogWarn(@"Can't invoke completion without messageThread");
}
}
}
}];
...
}
// Instance.cpp
void Instance::callJSFunction(
std::string &&module,
std::string &&method,
folly::dynamic &¶ms) {
callback_->incrementPendingJSCalls();
nativeToJsBridge_->callFunction(
std::move(module), std::move(method), std::move(params));
}
// NativeToJsBridge.cpp
void NativeToJsBridge::callFunction(
std::string &&module,
std::string &&method,
folly::dynamic &&arguments) {
...
// 入队
runOnExecutorQueue([this,
module = std::move(module),
method = std::move(method),
arguments = std::move(arguments),
systraceCookie](JSExecutor *executor) {
...
executor->callFunction(module, method, arguments);
});
}
// JSIExecutor.cpp
void JSIExecutor::callFunction(
const std::string &moduleId,
const std::string &methodId,
const folly::dynamic &arguments) {
...
// 如果callFunctionReturnFlushedQueue_不存在,表示native和js端直接通讯的bridge还未打通,此时需要手动bindBridge一下,打通直接通信的通道
if (!callFunctionReturnFlushedQueue_) {
bindBridge();
}
...
Value ret = Value::undefined();
try {
scopedTimeoutInvoker_(
[&] {
// 执行callFunctionReturnFlushedQueue_,返回一个消息列队。里面记录着js端对native的调用
ret = callFunctionReturnFlushedQueue_->call(
*runtime_,
moduleId,
methodId,
valueFromDynamic(*runtime_, arguments));
},
std::move(errorProducer));
} catch (...) {
std::throw_with_nested(
std::runtime_error("Error calling " + moduleId + "." + methodId));
}
// 调用native模块,即处理js对native端的调用
callNativeModules(ret, true);
}
callFunctionReturnFlushedQueue_
: 这个函数之前已经分析过,是native端调用,js端负责实现。最终把js端的消息队列返回给native端。因为在native调用js的过程中,有可能产生了js对native的调用。native需要处理一下。
自此 整个rn应用的启动就完毕了。
所有细节从代码层面已经梳理了一遍了。另外由于个人水平有限,无法确保梳理的一定是正确的,大家阅读文章的时候,请时刻怀疑质疑的想法去思考对照,文中观点仅供参考。希望大家多多指点文中有误的地方,及时纠正,避免误导他人。