一、导言
近年来,移动运用开发领域发展迅猛,不同的技能结构和渠道涌现出来,为开发者供给了更多的挑选和灵活性。React Native作为一种跨渠道移动运用开发结构,受到了广泛的关注和运用。它结合了React的声明式编程模型和原生移动运用的功用与体会,使开发人员能够用JavaScript构建高效、牢靠的原生运用。可是,React Native并非完全摆脱了原生开发的依靠。在iOS渠道上,React Native需求经过一定的集成和加载进程,使其能够在iOS设备上运转。本文将依据0.63.0版别的React Native,深化探讨iOS加载React Native的进程,协助开发者了解React Native在iOS上的作业原理。
首要,初始化React Native项目后,iOS/AppDelegate.m
文件会自动生成相关的代码片段,这些代码片段将用于在iOS运用中加载和初始化React Native结构,详细的代码内容如下:
- (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;
}
咱们发现在这部分代码中总共做了三件作业:
- 初始化一个
RCTBridge
; - 运用
RCTBridge
初始化一个RCTRootView
; - 将
RCTRootView
赋值给controller
的view
。
咱们先来看看 RCTRootView
究竟做了啥?和咱们本次想要解说的主题有何联系?
先阅RCTRootView
看到现在或许有同学在想,为啥还没直入主题,解说 iOS是怎么加载RN的呢?
之所以先简略叙述一下 RCTRootView
是为了让各位同学更好的了解 RCTRootView
和 RCTBridge
的联系;由于许多同学在网上查阅 RN 页面初始化发现有许多种办法,其实您会发现都是相同的。为何?请看下面:
// RCTRootView.h
@interface RCTRootView : UIView
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions;
// ...
@end
在 RCTRootView.h
声明文件中咱们发现如下两个讯息:
-
RCTRootView
继承自UIView
,标明其是一个 UI 展现的组件; -
RCTRootView
加载资源初始化有两种办法initWithBridge
和initWithBundleURL
两种办法
再来看看 RCTRootView.m
中 initWithBridge
和 initWithBundleURL
两种办法的完结:
//RCTRootView.m
@implementation RCTRootView {
RCTBridge *_bridge;
NSString *_moduleName;
RCTRootContentView *_contentView;
BOOL _passThroughTouches;
CGSize _intrinsicContentSize;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
{
// ...
if (self = [super initWithFrame:CGRectZero]) {
// ...
// 注册名为RCTJavaScriptWillStartLoadingNotification的音讯告诉,当jsbunlde即将加载时会触发bridgeDidReload
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidReload)
name:RCTJavaScriptWillStartLoadingNotification
object:_bridge];
// 注册名为RCTJavaScriptDidLoadNotification的音讯告诉,当jsbundle履行完结之后会会调用javaScriptDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
// 注册名为RCTContentDidAppearNotification的音讯告诉,当内容以及展现则会关闭加载进展条
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
// iOS版别的rn还支持展现loadingview哦
[self showLoadingView];
// Immediately schedule the application to be started.
// (Sometimes actual `_bridge` is already batched bridge here.)
[self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
}
return self;
}
// initWithBundleURL的完结
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
// 也是和initWithBridge相同,只是不需求自己手动实例化 RCTBridge
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions];
return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
- (void)javaScriptDidLoad:(NSNotification *)notification
{
// 获取到RCTBridge的实例batchedBridge(或许有点超前了,后边会将)
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _contentView.bridge) {
[self bundleFinishedLoading:bridge];
}
}
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
// ...
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge
reactTag:self.reactTag
sizeFlexiblity:_sizeFlexibility];
// 运用RCTBridge调用js办法,发动页面
[self runApplication:bridge];
// 展现页面
[self insertSubview:_contentView atIndex:0];
}
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag" : _contentView.reactTag,
@"initialProps" : _appProperties ?: @{},
};
// 履行JavaScript中的办法AppRegistry.runApplication
[bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
}
@end
查阅上面的源码咱们发现:
-
initWithBundleURL
和initWithBridge
两种完结办法,其本质都是运用的initWithBridge
将 RCTBridge 传入; - 在
RCTBridge
中RCTRootView
注册了各种监听,当资源在加载完结之后履行 react 代码并展现页面
好吧,我承认,假如您仔细看到这儿,是不是有许多疑问;什么 RCTRootView
, RCTBridge
, batchedBridge
, enqueueJSCall
,what , 这些都是个啥?下面我将结合RN加载的简易流程图一一告诉您(tips: 为什么先是简易流程?由于作者不想一下子灌输太多的概念让咱们蒙圈,详细流程请见之后的 RN 加载时序图)。
二、RCTBridge初始化
RCTBridge
是React Native结构中的一个重要组件之一。它作为JavaScript和原生代码之间的通讯桥梁,答应React Native运用程序和原生模块之间彼此调用和交流数据。
在React Native运用程序中,JavaScript代码运转在一个独立的JavaScript线程中,而原生代码运转在主线程中。 RCTBridge
负责管理这两个线程之间的通讯。它供给了一种机制,使得JavaScript代码能够调用原生模块的办法和函数,并传递参数。同样地,原生模块也能够经过RCTBridge将数据传递给JavaScript代码。
因此,能够说RCTBridge的初始化实践上是整个React Native加载的初始化进程。下面咱们来逐一查阅 RCTBridge
初始化进程中的几个重要办法:
// RCTBridge.m
// ...
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
// RCTBridge initWithDelegate的初始化办法都会进入到initWithDelegate_bundleURL_moduleProvider_launchOptions办法中
return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions];
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions
{
// RCTBridge initWithDelegate的初始化办法都会进入到initWithDelegate_bundleURL_moduleProvider_launchOptions办法中
return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions];
}
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)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
{
// 获取bridgeClass 默许是RCTCxxBridge
Class bridgeClass = self.bridgeClass;
// 只要bundleURL的值发生变化才会更新bundleURL
NSURL *previousDelegateURL = _delegateBundleURL;
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
_bundleURL = _delegateBundleURL;
}
// 初始化RTCxxBridge
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
// 发动RTCxxBridge
[self.batchedBridge start];
}
// 运用RCTCxxBridge也便是self.batchedBridge
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
// ...
RCTBridge
初始化办法总共做了如下几件作业:
- 调用
init
初始化RCTBridge
实例; - 调用
RTCxxBridge
的initWithParentBridge
办法初始化RTCxxBridge
实例 - 调用
self.batchedBridge
的start
办法发动
RCTCxxBridge start
上面部分 RCTBridge
初始化了 RCTCxxBridge
的实例,并调用了 start
办法,该办法的履行进程如图所示:
下面咱们来详细看看:
// RCTCxxBridge.m
- (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
[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)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
__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;
}
讲真,RCTCxxBridge
的 start
办法中,代码比较多;现在咱们来整理一下以上的 7 个进程:
-
发送
RCTJavaScriptWillStartLoadingNotification
音讯告诉以供RCTRootView
接收并处理; -
敞开 js 线程
_jsThread
, 并绑定了runRunLoop
,后续一切的 js 代码都在这个线程里面履行; -
注册一切 native modules:
- 一切的自界说 JS 和 iOS 通讯的 native module,包括 RN 自带的 Text, Date 等;
- 初始化阶段编写的 module,native module 会被转换成
RCTModuleData
分别存入字典和数组中; - 一切懒加载的 native modules,首要是在调试时,动态的注入 native modules。
-
预备 js 和 Native 之间的桥和 js 运转环境,初始化
JSExecutorFactory
实例; -
在 JS 线程中创立
RCTMessageThread
,初始化_reactInstance
(Instance 实例); -
异步加载 js 代码
-
等候 native modules 和 js 代码都加载完结则履行 js 代码;
咱们发现,react-native 中许多初始化的作业都是异步履行的,运用了 dispatch_group_t
,可是,终究都会运用 dispatch_group_notify
,也便是等候一切的异步初始化作业履行完结之后,才会履行终究的 js 代码履行操作。
JSExecutorFactory创立了什么
上面谈到了经过创立一个 JSExecutorFactory
来创立 Instance
,下面咱们就来看看 JSExecutorFactory
究竟是干嘛的。
JSExecutorFactory,顾名思义用于生产 JSExecutor
实例,JSExecutor
用于履行 JS,也是 JS 和 Native 之间的桥梁。不管是 Native call JS 仍是 JS call Native,JSExecutor
都起到了至关重要的效果。
当在 React Native 结构中运用 JSExecutorFactory
时,它的效果是创立 JavaScript 履行器。
JavaScript 履行器是 React Native 的中心组件之一,用于在移动设备上履行JS代码。它的首要责任是解析和履行JS代码,并将履行成果回来给运用程序。React Native 供给了多种履行器选项,包括 Hermes、JSC(JavaScriptCore)和 V8。
此外,经过 JSExecutorFactory
,开发人员能够依据功用和兼容性需求挑选不同的履行器。例如,Hermes 履行器在功用方面表现出色,因此从 React Native 0.60 版别开端成为默许履行器。而 JSC 和 V8 履行器在某些状况下或许具有更好的兼容性。
总结一下,JSExecutorFactory
是一个用于创立 JavaScript 履行器的接口,在 React Native 运用程序中起到要害效果。它答应开发人员依据运用需求挑选适宜的履行器,并经过 JSExecutorFactory
进行装备和运用。
// JSExecutorFactory.m
// 有一个createJSExecutor办法
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> __unused jsQueue)
{
// ...
// 默许的状况创立的是JSIExecutor;可是详细的createJSExecutor办法在什么时分调用,还得看下面的剖析;只是在这儿,咱们清楚了 createJSExecutor 创立的是 JSIExecutor
return std::make_unique<JSIExecutor>(
facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, std::move(installBindings));
}
Instance初始化
在运用JavaScript履行器时,咱们通常需求维护一个大局的履行器实例,以便在整个运用程序中同享和重复运用。这样能够避免重复创立和销毁履行器的开支,并保证在不同的代码段中能够同享相同的履行环境和上下文。同时经过将instance初始化为一个单例(Singleton)目标,咱们能够保证在整个运用程序中只要一个履行器实例存在。这样做的优点是能够减少资源的消耗,进步履行功率,并保证代码的一致性和可维护性。
经过上面的 RCTxCxBridge 的 start 办法调用链,咱们发现在 _initializeBridgeLocked
中初始化了_reactInstance
;
// RCTCxxBridge.m
- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
std::lock_guard<std::mutex> guard(_moduleRegistryLock);
// 初始化_reactInstance
_reactInstance->initializeBridge(
std::make_unique<RCTInstanceCallback>(self),
executorFactory,
_jsMessageThread,
[self _buildModuleRegistryUnlocked]);
_moduleRegistryCreated = YES;
}
// Instance.cpp
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry) {
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
// 运用JSExecutorFactory工厂创立一个JSExecutor来初始化NativeToJsBridge
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
// 履行initializeRuntime办法
nativeToJsBridge_->initializeRuntime();
// 当NativeToJsBridge被创立之后,jsi::Runtime就会存在,当然 前创立 js音讯行列也会存在,此刻能够将当时创立的NativeToJsBridge放入音讯行列中(此处咱们暂不详细深究音讯行列是干嘛的,后边会详细叙述)
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::lock_guard<std::mutex> lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
}
NativeToJsBridge的初始化
在实例化 JSExecutorFactory
并初始化 instance
之后,咱们还需求考虑初始化 NativeToJsBridge
。NativeToJsBridge
是一个重要的组件,用于完结JavaScript与本地代码之间的通讯。它供给了一种机制,使得JavaScript代码能够调用本地代码的功用,并且本地代码能够将成果回来给JavaScript环境。
经过初始化 NativeToJsBridge
,咱们能够保证在运用程序的各个部分都能够方便地运用桥接器进行JavaScript和本地代码之间的交互。这样,不管是在UI层仍是在后台逻辑中,咱们都能够轻松地调用本地办法或处理JavaScript的调用恳求。
NativeToJsBridge
的初始化是在 Instance
的 initializeBridge
办法中触发的,下面咱们就来看看详细 NativeToJsBridge
详细是什么做的:
// NativeToJsBridge.cpp
// NativeToJsBridge的初始化
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory *jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false)),
m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
m_executorMessageQueueThread(std::move(jsQueue)),
m_inspectable(m_executor->isInspectable()) {}
在初始化办法中首要做了如下几件作业:
- 其中
registry
和callback
作为入参生成了一个JsToNativeBridge
类型实例赋值给m_delegate
- 运用
JSExecutorFactory
和_delegate
创立了一个JSExecutor
,从RCTCxxBridge
中创立的JSExecutorFactory
得知此JSExecutor
便是JSIExecutor
- 设置音讯行列
此外,咱们从 NativeToJsBridge
得知一个名叫 ModuleRegistry
的参数;那么 ModuleRegistry
详细有什么效果请见下面的代码剖析:
/RCTCxxBridge.m
- (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())];
};
// 创立ModuleRegistry;经过_moduleDataByID<RCTModuleData>并创立一切自界说的NativeModule;也便是说 一切原生组件的加载其实把并不是在此处,而是存放在了一个名叫_moduleDataByID数组中
auto registry = std::make_shared<ModuleRegistry>(
createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback);
return registry;
}
// RCTCxxUtils.m
std::vector<std::unique_ptr<NativeModule>>
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
{
std::vector<std::unique_ptr<NativeModule>> nativeModules;
for (RCTModuleData *moduleData in modules) {
// 您能够测验着打印,您自界说的原生模块是否被加载
NSLog(@"原生模块: %@",moduleData.name);
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;
}
JSIExecutor
前面咱们现已将 RCTBridge
的初始化根本解说完结,还剩下终究一个部分,便是 JSIExecutor
; 从前面的部分咱们知道 NativeToJsBridge
持有 JSIExecutor
的实例(由于 JSIExecutor
是经过 JSExecutorFactory
创立的,JSExecutorFactory
是在 RCTCxxBridge
start 的时分初始化,可是正式调用 createJSExecutor
却是在 NativeToJsBridge
中);
和 Instance
、NativeToJsBridge
相同,JSIExecutor
首要用来 Native call JS,但他是比 Instance
和 NativeToJsBridge
更深层次的一个中心类;他会直接和 iOS 的 JavaScriptCore 进行通讯。
JSIExector特点
JSIExecutor
总共有几个要害的特点:
std::shared_ptr<jsi::Runtime> runtime_;
std::shared_ptr<ExecutorDelegate> delegate_;
std::shared_ptr<JSINativeModules> nativeModules_;
std::once_flag bindFlag_;
std::unique_ptr<RAMBundleRegistry> bundleRegistry_;
-
<jsi::Runtime> runtime_
:Runtime 类型指针,代表 JS 的运转时。这是一个抽象类,其实践上是由JSCRuntime
来完结的。JSCRuntime
完结了<jsi::Runtime>
接口,供给了创立 JS 上下文的功用,同时能够履行 JS。如下是JSCRuntime
的evaluateJavaScript
办法完结:
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;
// JSEvaluateScript是JavaScriptCore中用于履行js代码的东西
JSValueRef res =
JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
JSStringRelease(sourceRef);
if (sourceURLRef) {
JSStringRelease(sourceURLRef);
}
checkException(res, exc);
return createValue(res);
}
-
<ExecutorDelegate> delegate_
:ExecutorDelegate
类型的指针,这儿的ExecutorDelegate
是抽象类,实践是由JsToNativeBridge
完结的。// NativeToJsBridge.cpp NativeToJsBridge::NativeToJsBridge( JSExecutorFactory *jsExecutorFactory, std::shared_ptr<ModuleRegistry> registry, std::shared_ptr<MessageQueueThread> jsQueue, std::shared_ptr<InstanceCallback> callback) : m_destroyed(std::make_shared<bool>(false)), m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)), m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)), m_executorMessageQueueThread(std::move(jsQueue)), m_inspectable(m_executor->isInspectable()) {}
-
<JSINativeModules> nativeModules_
:JSINativeModules
由上层传入的ModuleRegistry
结构而成,而ModuleRegistry
又是在RCTxxBridge
中结构而成。
JSINativeModules
总共有两个办法:getModule
和 createModule
// JSINativeModules.cpp
Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name) {
if (!m_moduleRegistry) {
return nullptr;
}
std::string moduleName = name.utf8(rt);
const auto it = m_objects.find(moduleName);
if (it != m_objects.end()) {
return Value(rt, it->second);
}
// 调用createModule办法
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;
}
auto result =
m_objects.emplace(std::move(moduleName), std::move(*module)).first;
return Value(rt, result->second);
}
folly::Optional<Object> JSINativeModules::createModule(
Runtime &rt,
const std::string &name) {
bool hasLogger(ReactMarker::logTaggedMarker);
if (hasLogger) {
ReactMarker::logTaggedMarker(
ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
}
// runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
// JS端的函数__fbGenNativeModule调用终究就会走到这儿
if (!m_genNativeModuleJS) {
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
auto result = m_moduleRegistry->getConfig(name);
if (!result.hasValue()) {
return folly::none;
}
// 调用m_genNativeModuleJS函数,即__fbGenNativeModule
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";
folly::Optional<Object> module(
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
if (hasLogger) {
ReactMarker::logTaggedMarker(
ReactMarker::NATIVE_MODULE_SETUP_STOP, name.c_str());
}
return module;
}
看到 JSINativeModules
的两个办法,如同打开了一扇大门,可是又有一些疑问?
-
createModule
在getModule
中调用,那么getModule
再哪里被运用? -
_fbGenNativeModule
是什么?难道 js 和 RN 是经过_fbGenNativeModule
进行通讯的吗?
首要是是问题1,在 getModule
办法上按住 CMD 然后点击 caller,咱们发现大局仅有运用的当地在 JSIExecutor
中的 NativeModuleProxy
中:
// JSIExecutor.cpp
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
public:
// 结构函数 JSIExecutor实例作为NativeModuleProxy结构函数的入参
NativeModuleProxy(std::shared_ptr<JSINativeModules> nativeModules)
: weakNativeModules_(nativeModules) {}
// NativeModuleProxy 的 get办法 用于获取native module信息
Value get(Runtime &rt, const PropNameID &name) override {
return nativeModules->getModule(rt, name);
}
void set(Runtime &, const PropNameID &, const Value &) override {
throw std::runtime_error(
"Unable to put on NativeModules: Operation unsupported");
}
private:
std::weak_ptr<JSINativeModules> weakNativeModules_;
};
那么 NativeModuleProxy
这个类又是在哪里运用的呢?大局搜索 NativeModuleProxy
,您会发现只要一个当地在运用 NativeModuleProxy
,便是 JSIExecutor
的 initializeRuntime
办法,源码如下:
void JSIExecutor::initializeRuntime() {
SystraceSection s("JSIExecutor::initializeRuntime");
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));
// ...
}
而 JSIExecutor
的 initializeRuntime
的调用链为:
RCTxxBridge.start
->Instance.initializeBridge
-> NativeToJSBridge.initializeRuntime
->JSIExecutor.initializeRuntime
,说明在 runtime 时设置 nativeModuleProxy
是在初始化的时分履行的。
runtime 是一个 JSCRuntime
类型目标,经过调用 rumtime_->global()
获得一个大局的 global
目标。然后又经过 setProperty
办法给 global
目标设置了一个名为 nativeModuleProxy
的目标。日后(JS 侧的)global
目标经过”nativeModuleProxy”这个姓名即可访问到(native 侧的)NativeModuleProxy
。
// react-native/Libraries/BatchedBridge/NativeModules.js
let NativeModules: { [moduleName: string]: Object, ... } = {};
if (global.nativeModuleProxy) {
// 运用nativeModuleProxy
NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
const bridgeConfig = global.__fbBatchedBridgeConfig;
const defineLazyObjectProperty = require("../Utilities/defineLazyObjectProperty");
(bridgeConfig.remoteModuleConfig || []).forEach(
(config: ModuleConfig, moduleID: number) => {
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info) {
return;
}
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID),
});
}
}
);
}
经过如上的代码,咱们能够得到一个等式:JS 侧的 NativeModules == JS 侧的 global.nativeModuleProxy == native 侧 NativeModuleProxy = native 侧一切原生及自界说的 Native Module。
接下来,咱们答复问题二:_fbGenNativeModule
是什么?难道 js 和 RN 是经过 _fbGenNativeModule
进行通讯的吗?
首要,在 native 中经过 runtime 的办法获取到 js 的大局目标 _fbGenNativeModule
;
// JSINativeModules.cpp
folly::Optional<Object> JSINativeModules::createModule(
Runtime &rt,
const std::string &name) {
bool hasLogger(ReactMarker::logTaggedMarker);
if (hasLogger) {
ReactMarker::logTaggedMarker(
ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
}
// runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
// JS端的函数__fbGenNativeModule调用终究就会走到这儿
if (!m_genNativeModuleJS) {
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
// 调用m_genNativeModuleJS函数,即__fbGenNativeModule
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";
folly::Optional<Object> module(
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
return module;
}
接着,咱们来看看 js 端是否有 _fbGenNativeModule
这个大局函数:
// react-native/Libraries/BatchedBridge/NativeModules.js
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);
const methodType = isPromise ? "promise" : isSync ? "sync" : "async";
module[methodName] = genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
} else {
}
return { name: moduleName, module };
}
// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;
还真别说在 JS 端真存在一个 _fbGenNativeModule
的大局办法;其首要效果便是方便 native 调用 js 代码对应 module 下的办法。
_fbGenNativeModule
的首要效果是方便 native 侧调用 js 的模块下的办法;也便是native to JS。
至此,在 RCTBridge
初始化的进程解说结束,终究咱们运用一个时序图来总结之前提到的各个首要类的履行顺序:
三、JS加载
经过上面的部分咱们了解到 RN 代码加载运用的是 RCTJavaScriptLoader
的 loadBundleAtURL_onProgress_onComplete
的办法;咱们能够运用 onProgress
监听加载进展,在 onComplete
中依据 error
是否为空判别加载是否成功,并且能够运用 source.data
获取到加载的二进制 JS 代码。
那么 RCTJavaScriptLoader
又是怎么加载的?
// RCTJavaScriptLoader.m
+ (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
由于同步加载代码较长笔者暂时保留重要部分,有爱好同学可自行查阅。
+ (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 件作业:
- 判别 bundle 是否在本地,由于同步加载只加载本地 bundle;不然直接报错;
- 运用
fopen
读取本地 bundle; - 经过 bundle 的前 4 个字节来判别 bundle 归于什么类型:
RAMBundle
,String
,BCBundle
; - 回来 bundle 的二进制数据.
读到这儿,是不是对 RAMBundle
, String
, BCBundle
这三种类型有疑问,他们分别是干嘛的;笔者就在此处给咱们解答疑问。
在 react-native 履行 JS 代码之前,必须将代码加载到内存中并进行解析。假如您加载了一个 50MB 的一般 Bundle,那么一切的 50MB 都必须被加载和解析才能被履行。RAM 格式的 Bundle 则对此进行了优化,即发动时只加载 50MB 中实践需求的部分,之后再逐渐按需加载更多的包。
-
RAMBundle
: RAM bundle 相比一般的 bundle 优点在于能够运用懒加载的办法将 module 注入到 JSC 中;RAMBundle
是用ram-bundle
指令打出来的bundle,它除了生成整合的 js 文件外,还会生成各个独自的未整合 js 文件,全部放在 js-modules 目录下, bundle 头四个字节固定为0xFB0BD1E5
.RAMBundle
的运用及设置,概况请见官网(opens new window) -
BCBundle
: BCBundle 是 js 字节码 bundle 类型; 不答应运用; -
String
: 一般的 bundle
异步加载JS
上面介绍了同步加载 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];
}
果不其然,异步加载首要做了两件作业:
- 假如是本地的 Bundle 则运用异步加载本地的办法;
- 假如不是本地 Bundle 则实例化一个RCTMultipartDataTask下载使命;异步下载 Bundle;
关于 Bundle 的加载信任讲到这儿咱们现已了然于心;在这儿简略做一个总结:
- RN 默许优先判别是否支持本地 Bundle 同步加载;假如能够则:
- 判别 bundle 是否在本地,由于同步加载只加载本地 bundle;不然直接报错;
- 运用 fopen 读取本地 bundle;
- 经过 bundle 的前 4 个字节来判别 bundle 归于什么类型:
RAMBundle
,String
,BCBundle
; - 回来 bundle 的二进制数据.
- 不然运用异步加载 Bundle的办法
-
假如是本地的 Bundle 则运用异步加载本地的办法;
-
假如不是本地 Bundle 则实例化一个
RCTMultipartDataTask
下载使命;异步下载 Bundle; -
加载完结 Bundle 就该履行 JS 代码咯。
至此JS加载的进程现已解说结束,终究咱们将完结的流程总结为如下图:
四、履行JS代码
一旦JavaScript代码加载完结并预备就绪,咱们能够开端履行它。在React Native中,这个进程是经过RCTCxxBridge的start办法完结的。在这个办法中,JavaScript代码的履行会等候jsbundle文件加载完结,并保证原生模块也现已加载结束,然后才会进行履行,详细的完结如下:
// RCTCxxBridge.m
// 等候 jsbundle的和native modules完结加载后则开端履行代码
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
RCTCxxBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode sync:NO];
}
});
// js代码的履行
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync{
// js代码履行回调
dispatch_block_t completion = ^{
// 当js代码履行完结,需求改写js履行事情行列
[self _flushPendingCalls];
// 在主线程中告诉RCTRootView; js代码现已履行结束;当RCTRootView接收到告诉就会挂载展现
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self->_parentBridge
userInfo:@{@"bridge" : self}];
[self ensureOnJavaScriptThread:^{ // 定时器继续履行 [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
}];
});
};
if (sync) {
// 同步履行js代码
[self executeApplicationScriptSync:sourceCode url:self.bundleURL];
completion();
} else {
// 异步履行js代码
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
}
[self.devSettings setupHotModuleReloadClientIfApplicableForURL:self.bundleURL];
}
经过上面咱们简略了解到:
- 依据
sync
来挑选是同步履行 js 仍是异步履行 js; - js 履行完结之后会进入事情回调
completion
,在事情回调中咱们会改写当时的 js 履行行列并发送告诉给 RCTRootView;
上面讲到同步履行 js 和异步履行 js 的两个办法;enqueueApplicationScript
和 executeApplicationScriptSync
,查阅源码,咱们知道这两个办法都是调用的同一个办法 executeApplicationScript
;
// RCTCxxBridge.m
- (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");
}
}];
}
假如您看到这儿是不是突然觉得豁然开朗;js 代码的履行运用的是 _reactInstance
-> loadRAMBundle
或许 reactInstance
-> loadScriptFromString
办法;,可是咱们之前也现已讲到 reactInstance
会初始化 NativeToJsBridge
,NativeToJsBridge
会运用 factory
初始化 JSIExecutor
,所以咱们能够以为:其实 Instance
是 NativeToJsBridged
的包装,NativeToJsBridge
又是 JSIExecutor
的包装;
为了证明这一点,咱们一同来看一下详细的源码:(此处拿 loadScriptFromString
为例)
//Instance.cpp
void Instance::loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
SystraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
if (loadSynchronously) {
// 同步加载Bundle
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
} else {
// 异步加载Bundle
loadBundle(nullptr, std::move(string), std::move(sourceURL));
}
}
void Instance::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
callback_->incrementPendingJSCalls();
SystraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
// 终究仍是调用的NativeToJsBridge的加载办法
nativeToJsBridge_->loadBundle(
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}
void Instance::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
std::unique_lock<std::mutex> lock(m_syncMutex);
m_syncCV.wait(lock, [this] { return m_syncReady; });
// 终究仍是调用的NativeToJsBridge的加载办法
nativeToJsBridge_->loadBundleSync(
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}
// NativeToJsBridge.cpp
void NativeToJsBridge::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
if (bundleRegistry) {
m_executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
// 调用是JSIExecutor的加载办法
m_executor->loadBundle(
std::move(startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
}
在 ctx 中履行 JS 源码后,会初始化 JS 环境,BatchedBridge.js
、NativeModules.js
中的初始化代码也会履行。在 BatchedBridge.js
中,创立了一个名为 BatchedBridge
的 MessageQueue
,并设置到 global
的 _fbBatchedBridge
特点里,这个特点后边会用到。在初始化 JS 环境的时分,会加载到某些 NativeModule,这些 module 才会被初始化,即调用到 native 侧 JSINativeModules
的getModule
办法。当相关的 Module 都加载完之后,evaluateScript
办法履行完,JS 环境初始化结束。
不知您注意到在 JSIExecutor
履行 jsbundle 之后有一个 flush
办法没?
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
办法中的内容比较多,但总的功用如下:
- 在 ctx 中履行 JS 源码,会初始化 JS 环境,
BatchedBridge.js
,NativeModules.js
中的初始化代码也会履行。在BatchedBridge.js
中,创立了一个名为BatchedBridge
的MessageQueue
,并设置到global
的__fbBatchedBridge
特点里; - 假如
JSIExecutor
的flushedQueue
函数不为空,则经过函数flushedQueue
获取待调用的办法queue
,然后履行callNativeModules
; - 经过
__fbBatchedBridge
作为特点 key 去global
中取对应的值也便是batchedBridge
,batchedBridge
本质上是 JS 侧的MessageQueue
类实例化的一个目标; - 假如获取到的
batchedBridge
为空或许还未绑定,则先将 js 函数和 native 进行绑定,然后履行callNativeModules
,callNativeModules
的首要效果其实便是经过invoke
的办法履行 native modules 中办法。
总结
总结起来,iOS加载React Native的进程是一个杂乱并且精妙的进程,详细进程如下。从初始化JavaScript环境到加载和烘托React Native组件,每个进程都严密相连,一同构建起无缝的跨渠道运用体会。
- 初始化了
RCTBridge
,并在RCTxxBridge
中敞开 JS 履行线程,一切的 js 代码都在此线程履行; - 初始化一切的 native modules(包括自界说的,RN 自带的以及经过 options 参数传递的);
- 在 JS 线程中运用
JSExecutorFactory
创立了一个NativeToJsBridge
实例,并运用NativeToJSBridge
实例创立出JSIExecutor
;其中JSIExecutor
才是整个 JS 加载初始化及履行的中心; - 在初始化上述的
JSIExecutor
的同时,异步加载 JS 代码并等候初始化完结和 JS 代码加载完结; - 一旦JS代码加载完结并初始化结束,
JSIExecutor
开端履行JS代码; - 履行完结 js 代码后会调用
flush
办法,去调用 native modules。
经过深化了解这个进程,咱们能够更好地了解iOS与React Native的交互办法,并在开发中愈加高效地运用这一技能。不管是为了提高运用功用仍是处理潜在的问题,对iOS加载React Native的进程进行深化研究都是非常有价值的。期望本文能为读者供给清晰的指引,协助您们在iOS渠道上构建出出色的React Native运用。
hi, 我是快手社交的 MeiLinG
快手社交技能团队正在招贤纳士! 咱们是公司的中心事务线, 这儿云集了各路高手, 也充满了机会与挑战. 伴随着事务的高速发展, 团队也在快速扩张. 欢迎各位高手参加咱们, 一同创造世界级的产品~ 热招岗位: Android/iOS 高档开发, Android/iOS 专家, Java 架构师, 产品经理, 测试开发… 大量 HC 等你来呦~ 内部引荐请发简历至 >>>咱们的邮箱: social-tech-team@kuaishou.com <<<, 备注我的诨名成功率更高哦~