致敬
开始准备写这一篇文章的时候,中国的新型冠状病毒肺炎疫情还在继续,累积确诊81058人,现存确诊10827人。这篇文章写完的时候,累计确诊81501人,现存确认5846人。疫情已经持续了3个月,但也终将过去。毫无疑问,在这漫长的3个月的时间里,很多与疫情抗争的工作人员都是非常辛苦的,再次感谢&致敬。
前言
这是一篇原理性文章,也是一篇源码分析文章。这篇文章是笔者学习RN源码过程中的一篇记录文章,主要记录了程序从启动之初到开始执行JS源码的整个流程。从AppDelegate的application:didFinishLaunchingWithOptions:说起,全流程涉及到关键类的初始化工作和JavaScript的执行以及JS&Native之间的通信。围绕bridge的初始化、JS源码的加载、JS源码的执行、Native调用JS、JS调用Native展开分析。内容虽然很长,但其实很浅,大部分都是源码,并没有加入自己太多的思考,耐心看完就可以理解。
本文篇幅很长的原因是笔者贴出了大量的RN源码。文章中的源码已经做了精简,如果想看完整的代码还是建议参考RN源码。笔者主要删除了源码中与逻辑无强关联的代码。比如debug环境的宏、锁、调试相关的代码、健壮性相关的代码、错误处理相关的代码、代码执行耗时相关的代码。删除这些代码不会影响对源码的阅读和理解,请大家放心。
阅读这篇文章你最好具备以下条件:你应该是一个iOS开发者,本文是站在一个iOS工程的角度分析RN的源码,当然如果你能看懂Objective-C代码也是可以的。你应该对RN有所了解,最好是使用RN开发过一些需求。你应该对JS有所了解,本文会涉及少量JS代码。最后,你最好具备一些C++的知识,RN源码中存在大量的C++代码,不需要会写,了解C++语法能看懂C++代码即可。当然,如果你认为万物皆对象,以上条件都可以忽略,那么让我们开始吧》》》
本文基于React Native 0.61.5进行分析。
名词
本文中主要涉及到以下几个类:RCTBridge、RCTCxxBridge、Instance、NativeToJsBridge、JsToNativeBridge、JSIExecutor、RCTRootView。他们的关系大概如下(JSIExecutor是本文涉及到的最内层的类):
React Native关键类关系图开始
我们新建一个名为NewProject的RN的iOS工程。可以看出AppDelegate.m的application: didFinishLaunchingWithOptions:方法实现是这样的:
- (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:@"NewProject"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
// 3.设置rootViewController
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
以上代码可以看出,application:didFinishLaunchWithOptions:主要做了3件事:
1.初始化一个RCTBridge实例
2.再用RCTBridge实例初始化一个rootView
3.用rootView配置一个rootViewController
第三步用rootView初始化一个rootViewController没什么可说的,本片文章我们主要窥探初始化RCTBridge和RCTRootView。
RCTBridge初始化
RCTBridge初始化是重点也是难点,虽然叫RCTBridge的初始化,但实际上不仅仅是初始化一个RCTBridge实例那么简单,在其背后还有RCTCxxBridge、NativeToJSBridge、JSExecutor(JSIExecutor生产环境/RCTObjcExecutor调试环境)、JsToNativeBridge的初始化,这里仅作为一个了解,不必纠结,后面会详细介绍。先来看一下appDelegate中调用的RCTBridge的初始化的源码实现:
// RCTBridge.m
- (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;
// 只有delegate返回的bundleURL发生变化才更新_bundleURL
NSURL *previousDelegateURL = _delegateBundleURL;
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
_bundleURL = _delegateBundleURL;
}
// 初始化self.batchedBridge,也就是RCTCxxBridge
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
// 启动RCTCxxBridge
[self.batchedBridge start];
}
// self.bridgeClass
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
上面RCTBridge的初始化方法是创建了一个RCTBridge实例,通过调用私有方法setUp对bridge进行配置。setUp主要做了2件事情:
1.初始化self.batchedBridge,也就是RCTCxxBridge实例
2.启动self.bathedBridge(RCTCxxBridge实例)
self.batchedBridge的start源码如下:
// RCTCxxBridge.mm
- (void)start {
// 1.提前设置并开启JS线程 _jsThread
_jsThread = [[NSThread alloc] initWithTarget:[self class]
selector:@selector(runRunLoop)
object:nil];
_jsThread.name = RCTJSThreadName; // @"com.facebook.react.JavaScript"
_jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
[_jsThread start];
dispatch_group_t prepareBridge = dispatch_group_create();
// 2.初始化注册native module
[self registerExtraModules];
// 初始化所有不能被懒加载的native module
[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
[self registerExtraLazyModules];
_reactInstance.reset(new Instance);
__weak RCTCxxBridge *weakSelf = self;
// 准备executor factory
std::shared_ptr<JSExecutorFactory> executorFactory;
if (!self.executorClass) {
if ([self.delegate conformsToProtocol:@ (RCTCxxBridgeDelegate)]) {
id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
}
if (!executorFactory) {
executorFactory = std::make_shared<JSCExecutorFactory>(nullptr);
}
} else {
id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
}));
}
// 3.module初始化完就初始化底层Instance实例,也就是_reactInstance
// 在JS线程初始化_reactInstance、RCTMessageThread、nativeToJsBridge、JSCExecutor
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 4.异步加载JS代码
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self loadSource:^(NSError *error, RCTSource *source) {
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
}];
// 5.等待native moudle 和 JS 代码加载完毕后就执行JS
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];
}
});
}
这个start方法很关键,是RCTCxxBridge中最主要的方法。因为做的事情较多且掺杂着部分C++代码, 所以让人觉得很复杂,梳理一下,其实start中主要做了5件事:
1.创建并开启一个JS线程(_jsThread),该线程绑定了一个runloop,顾名思义,这个线程就是用来执行JS代码的,后续所有的js代码都是在这个线程里执行。
2.初始化注册所有要暴露给js调用的native module,每个native module类都封装成一个RCTModuleData实例,如果需要在主线程中创建某些类的实例,则会在主线程中去创建实例。这些RCTModuleData会分别存储在字典和数组里。
3.准备JS和Native之间的桥和JS运行环境,初始化JSExecutorFactory实例(顾名思义,JSExecutorFactory是一个JSExecutor的工厂,也就是负责生产JSExecutor实例的工厂),然后在JS线程中创建JS的RCTMessageThread,初始化_reactInstance(Instance实例)、nativeToJsBridge(NativeToJsBridge实例)、executor(JSIExecutor实例)。以上这些事情主要是在_initializeBridge:方法中完成的,此处作为了解,后面详细分析。
4.异步加载JS源码
5.native module和JS代码都加载完毕后就执行JS代码
以上提到了三个C++类Instance、NativeToJsBridge、JSIExecutor,他们都是native call JS的桥梁。但有什么区别呢?其实他们的关系是Instance->NativeToJsBridge->JSIExecutor。即Instance中创建并持有NativeToJsBridge实例,NativeToJsBridge中又创建并持有JSIExecutor实例。换句话说,Instance是对NativeToJsBridge的封装,NativeToJsBridge是对JSIExecutor的封装。
上述源码里用到一个叫prepareBridge的dispatch_group_t,虽然名称叫prepareBridge,但其实是一个dispatch_group_t。dispatch_group_t和dispatch_group_notify联合使用保证异步代码同步按顺序执行,也就是被添加到group中的任务都做完了之后再执行notify中的任务(但group中的多个任务的执行顺序是无序的)。有很多初始化工作是异步并行的,运行JS源码是在所有准备工作之后才能进行,所以用了dispatch_group_t和dispatch_group_notify机制来确保这个问题。
在上述源码里我们看到了一个名为ensureOnJavaScriptThread:的方法,如下:
// RCTCxxBridge.mm
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
看名字就知道,ensureOnJavaScriptThread:是RCTCxxBridge里专门将block放在JS线程中执行的方法。他的目的就是确保待执行的block能在JS线程执行,所以他接收一个block作为参数,然后判断当前线程是否是先前创建的JS线程,如果是则立即在JS线程同步执行block,否则切换到JS线程执行block。源码如下:
// RCTCxxBridge.mm
- (void)ensureOnJavaScriptThread:(dispatch_block_t)block
{
if ([NSThread currentThread] == _jsThread) {
[self _tryAndHandleError:block];
} else {
[self performSelector:@selector(_tryAndHandleError:)
onThread:_jsThread
withObject:block
waitUntilDone:NO];
}
}
大家常说JS是单线程的,在RN里就是这样的。native侧创建了一个专门服务于JS的线程,然后绑定了一个runloop不让这个JS线程退出,后续JS代码都是在这个线程里执行。
刚才上面只是穿插介绍了ensureOnJavaScriptThread的作用。回过头来看start方法中ensureOnJavaScriptThread:的block主要是在JS线程执行了weakSelf _initializeBridge:executorFactory; 接下来看下_initializeBridge:到底做了哪些事情,源码如下:
// RCTCxxBridge.mm
- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
RCTAssertJSThread();
__weak RCTCxxBridge *weakSelf = self;
_jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
});
if (_reactInstance) {
[self _initializeBridgeLocked:executorFactory];
}
}
- (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
// 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;
}
如上,通过assert不难看出,RCTCxxBridge的 _initializeBridge:方法确实是在JSThread中调用的。且 _initializeBridge:方法主要做了2件事:
1.创建一个名为_jsMessageThread的RCTMessageThread实例,并被RCTCxxBridge持有(可以看出messageThread实际上是由runloop实现的)
2.调用_initializeBridgeLocked:传入RCTExecutorFactory初始化bridge(nativeToJsBridge实例)
_initializeBridgeLocked:的实现更简单,_initializeBridgeLocked:内部调用了_reactInstance的initializeBridge方法继续初始化bridge(NativeToJsBrige实例)。如注释所述,这个方法是异步调用的,但是所有经由_reactInstance实例对JS方法的调用都会被Instance中名为m_syncReady这个成员变量给锁住。
此处先忽略上面的第四个参数self _buildModuleRegistryUnlocked ,继续看_reactInstance的initializeBridge方法:
// 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) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
std::lock_guard<std::mutex> lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
}
不难看出,在jsQueue(MessageQueueThread)线程里最主要创建了native->JS的桥,即nativeToJsBridge_。然后instance持有了这个nativeToJsBridge_以及其他2个外部传进来的参数 callback、moduleRegistry。至此Instance的初始化就结束了。下面我们来看一下这四个参数:
第一个参数是InstanceCallback类型的回调,用于底层执行结束后往上层回调,实际上调用方传递的是self,即RCTCxxBridge实例。
第二个参数是JSExecutorFactory,即生产JSExecutor的工厂实例。Instance内部会使用这个facotry获得一个JSExecutor实例。
第三个参数就是我们在外面创建的MessageueueThread。
第四个参数moduleRegistry是ModuleRegistry类型的实例。moduleRegistry里面包含了所有的Native Module信息,即RCTModuleData。即将前面生成的所有RCTModuleData传给了_reactInstance。至此我们知道self _buildModuleRegistryUnlocked实际上是返回了一个RCTmoduleRegistry实例。
下面简单介绍后3个参数:
JSExecutorFactory
JSExecutorFactory,顾名思义用于生产JSExecutor实例,JSExecutor用于执行JS,也是JS和Native之间的桥梁。无论是Native call JS还是JS call Native,JSExecutor都起到了至关重要的作用。生产环境下使用的是JSCExecutorFactory,返回JSIExecutor用于执行JS,开发环境使用的是RCTObjcExecutorFactory,返回RCTObjcExecutor通过websocket链接chrome执行JS。
// JSExecutor.h
class JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() {}
};
MessageQueueThread
MessageQueueThread类型对象用于提供队列执行。这里是由RCTMessageThread来实现,内部用的是CFRunLoop来实现。
除RCTMessageThread之外,另一个实现是DispatchMessageQueueThread,我们不做详细介绍。
// MessageQueueThread.h
namespace facebook {
namespace react {
class MessageQueueThread {
public:
virtual ~MessageQueueThread() {}
virtual void runOnQueue(std::function<void()>&&) = 0;
virtual void runOnQueueSync(std::function<void()>&&) = 0;
virtual void quitSynchronous() = 0;
};
}}
ModuleRegistry
上面说了moduleRegistry中包括了所有native module信息,即RCTModuleData。这还要从我们刚才忽略的self _buildModuleRegistryUnlocked方法说起,_buildModuleRegistryUnlocked方法主要负责构建一个RCTModuleRegistry实例并返回,如下:
// RCTCxxBridge.mm
- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked
{
__weak __typeof(self) weakSelf = self;
auto registry = std::make_shared<ModuleRegistry>(
createNativeModules(_moduleDataByID, self, _reactInstance),
moduleNotFoundCallback);
return registry;
}
// RCTCxxUtils.mm
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) {
if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
nativeModules.emplace_back(std::make_unique<CxxNativeModule>(
instance,
[moduleData.name UTF8String],
// moduleData.instance就是native module实例对象
[moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },
std::make_shared<DispatchMessageQueueThread>(moduleData)));
} else {
nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge, moduleData));
}
}
return nativeModules;
}
可以看出,上面使用_moduleDataByID初始化并返回了一个ModuleRegistry实例。_moduleDataByID是一个含有若干个RCTModuleData对象的数组。在https://cloud.tencent.com/developer/article/1597259中我们介绍了_moduleDataByID中的RCTModuleData实际上就是在程序启动后load各个class的时候被收集的。有必要提一下,上面源码中的moduleData.instance其实就是native module的实例对象。通过如下代码可窥见一斑:
// RCTModuleData.mm
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
{
return [self initWithModuleClass:moduleClass
moduleProvider:^id<RCTBridgeModule>{ return [moduleClass new]; }
bridge:bridge];
}
- (instancetype)initWithModuleClass:(Class)moduleClass
moduleProvider:(RCTBridgeModuleProvider)moduleProvider
bridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_moduleClass = moduleClass;
_moduleProvider = [moduleProvider copy];
[self setUp];
}
return self;
}
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_instance = instance;
_moduleClass = [instance class];
[self setUp];
}
return self;
}
至此,RN中Instance实例(_reactInstance)的初始化已经介绍完了,接下来介绍NativeToJSBridge。为什么要介绍NativeToJsBridge?因为我们上面说了Instance是对NativeToJSBridge的封装,就像UIView是对CALayer的封装一样,可见NativeToJSBridge比Instance更加接近底层(实际JSExecutor比NativeToJSBridge更加底层,我们后面详细的说明)。
NativeToJSBridge
NativeToJsBridge的主要作用是负责管理所有native对JS的调用,并且也管理了executor们和他们的线程。NativeToJsBridge的所有函数可以在任意线程被调用。除非某些方法是为了同步加载Application Script,否则所有的方法都是在JSQueue线程排队等候执行的,且这些函数会被立即返回。这也说明大部分Native call JS的方法都是在jsQueue这个线程执行的,而jsQueue实际上就是messageQueueThread。
在上面Instance::initializeBridge函数中,我们知道Instance创建了一个名为nativeToJsBridge_的NativeToJSBridge的实例并被Instance实例持有。如下:
// 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) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
// 初始化nativeToJsBridge_成员变量
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
});
}
可以看出nativeToJsBirdge的类型是NativeToJsBridge。下面我们来看下NativeToJsBridge类的具体定义。
NativeToJSBridge定义
// NativeToJsBridge.cpp
class NativeToJsBridge {
public:
friend class JsToNativeBridge;
// 必须在主线程调用
NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback);
virtual ~NativeToJsBridge();
// 传入module ID、method ID、参数用于在JS侧执行一个函数
void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args);
// 通过callbackId调用JS侧的回调
void invokeCallback(double callbackId, folly::dynamic&& args);
// 开始执行JS application. 如果bundleRegistry非空,就会使用RAM的方式 读取JS源码文件
// 否则就假定 startupCode 已经包含了所有的JS源码文件
void loadApplication(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupCode,
std::string sourceURL);
void loadApplicationSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupCode,
std::string sourceURL);
private:
std::shared_ptr<JsToNativeBridge> m_delegate;
std::unique_ptr<JSExecutor> m_executor;
std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
};
如上,NativeToJsBridge是一个C++类,它主要有如下3个重要的成员变量:
-
<JsToNativeBridge> m_delegate
m_delegate是JsToNativeBridge类型的引用,主要用于JS call Native
-
<JSExecutor> m_executor
JSExecutor类型引用,主要用于执行Native call JS,实际上生产环境使用的是JSIExecutor;调试环境使用的是RCTObjcExecutor
-
<MessageQueueThread> m_executorMessageQueueThread
MessageQueueThread类型引用,内部是由runloop实现的,由外部传递,用于队列管理。这里的外部传递是指m_executorMessageQueueThread并非NativeToJsBridge自己初始化的,而是作为初始化NativeToJsBridge的参数由上层传递进来的。如果你还记得NativeToJsBridge是在Instance::initializeBridge中初始化的,那么你就知道这个m_executorMessageQueueThread最终起源于RCTCxxBridge中的_jsMessageThread,进而由一步一步的函数调用,穿山越岭传递进来的。所以,NativeToJsBridge的m_executorMessageQueueThread就是 了RCTCxxBridge的_jsMessageThread。
除了以上3个关键的属性之外,NativeToJsBridge还定义了4个函数:
-
void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args);
这个函数的意义就是通过module ID和method ID以及参数去调用JS方法
-
void invokeCallback(double callbackId, folly::dynamic&& args);
这个函数的意义就是通过callbackId和参数触发一个JS的回调。通常是JS call Native method之后,native把一些异步的执行结果再以callback的形式回调给JS。
-
void loadApplication(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupCode,
std::string sourceURL);这个方法的作用是执行JS代码,他还有一个兄弟叫做loadApplicationSync,顾名思义,他兄弟是一个同步函数,所以他自己就是异步执行JS代码。
NativeToJsBridge构造函数
上面说了NativeBridge的3个关键属性和4个关键方法,接下来我们说下他的构造函数,接触过C++的开发者应该知道,C++中类的构造函数和类同名,如下是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()) {}
上面我们在Instance::initializeBridge中就已经调用过NativeToJsBridge的构造函数创建了一个NativeToJsBridge实例并被赋值给Instance实例的成员变量nativeToJsBridge_。在NativeToJSBridge构造函数的后面有一个初始化列表,其中registry和callback作为入参生成了一个JsToNativeBridge类型实例赋值给m_delegate。jsExecutorFactory又通过m_delegate和jsQueue生产了一个executor赋值给m_executor(m_delegate最终是给m_executor使用的,在生产环境下,jsQueue对于executor也是无用的),m_executorMessageQueueThread最后指向了jsQueue。
JSIExecutor
和Instance、NativeToJsBridge一样,JSIExecutor主要用来Native call JS,但他是比Instance和NativeToJsBridge更深层次的一个核心类,换句话说,我们可以把NativeToJsBridge理解为JSIExecutor的包装(而Instance又是对NativeToJsBridge的包装),对Instance的调用最终都会走到NativeToJsBridge,对NativeToJsBridge的调用最终都会走到JSIExecutor,比如getJavaScriptContext、callFunction、invokeCallback这些方法。他们的调用顺序是Instance->NativeToJsBridge->JSIExecutor。上面我们说了,在NativeToJsBridge的构造函数中jsExecutorFactory使用JsToNativeBridge实例m_delegate和jsQueue创建了m_executor(实际上生产环境下只用了m_delegate)。这里我们主要以生产环境的JSIExecutor为例介绍。调试模式下请参考RCTObjcExecutor,他们都继承自JSExecutor。下面是两种环境下executor的创建方式,生产环境的JSIExecutor通过JSCExecutorFactory生产,调试模式下的RCTObjcExecutor通过RCTObjcExecutorFactory生产,如下:
// JSCExecutorFactory.mm
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> __unused jsQueue) {
return folly::make_unique<JSIExecutor>(
facebook::jsc::makeJSCRuntime(),
delegate,
JSIExecutor::defaultTimeoutInvoker,
std::move(installBindings));
}
// RCTObjcExecutor.mm
std::unique_ptr<JSExecutor> RCTObjcExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) {
return std::unique_ptr<JSExecutor>(
new RCTObjcExecutor(m_jse, m_errorBlock, jsQueue, delegate));
}
JSIExecutor主要的几个关键属性:
- <jsi::Runtime> runtime_
Runtime类型指针,代表JS的运行时。这是一个抽象类,其实际上是由JSCRuntime来实现的。JSCRuntime实现了<jsi::Runtime>接口,提供了创建JS上下文的功能,同时可以执行JS。如下是JSCRuntime的evaluateJavaScript方法实现:
// JSCRuntime.cpp
jsi::Value JSCRuntime::evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string& sourceURL) {
std::string tmp(
reinterpret_cast<const char*>(buffer->data()), buffer->size());
JSStringRef sourceRef = JSStringCreateWithUTF8CString(tmp.c_str());
JSStringRef sourceURLRef = nullptr;
if (!sourceURL.empty()) {
sourceURLRef = JSStringCreateWithUTF8CString(sourceURL.c_str());
}
JSValueRef exc = nullptr;
JSValueRef res = JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
return createValue(res);
}
- <ExecutorDelegate> delegate_
ExecutorDelegate类型的指针,这里的ExecutorDelegate是抽象类,实际是由JsToNativeBridge实现的。也即JSIExecutor引用了JsToNativeBridge实例。还记得NativeToJsBridge中的JsToNativeBridge类型的成员变量m_delegate吗?其实这里的delegate_就是NativeToJsBridge中的m_delegate。
- <JSINativeModules> nativeModules_
JSINativeModules由上层传入的ModuleRegistry构造而成,同时会将ModuleRegistry中包含的本地模块配置信息通过”__fbGenNativeModule”保存到JS端。
JSINativeModules有个getModule方法,getModule方法内又调用了 createModule方法,createModule方法生成了module信息,源码如下:
// JSINativeModules.cpp
Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
std::string moduleName = name.utf8(rt);
// 调用createModule方法
auto module = createModule(rt, moduleName);
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) {
if (!m_genNativeModuleJS) {
// runtime获取名为__fbGenNativeModule的函数指针赋值给m_genNativeModuleJS
// JS端的函数__fbGenNativeModule调用最终就会走到这里。
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
auto result = m_moduleRegistry->getConfig(name);
// 调用m_genNativeModuleJS函数,即__fbGenNativeModule
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
folly::Optional<Object> module(
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
// 返回生成的module
return module;
}
那到这里你会问JSINativeModules负责createModule并提供了可以访问某个module的接口getModule。那是谁调用的JSINativeModules的getModule呢?全局搜索getModule不难发现,getModule是在一个名为NativeModuleProxy的get方法里调用的,如下:
// JSIExecutor.cpp
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
public:
// 构造函数 JSIExecutor实例作为NativeModuleProxy构造函数的入参
NativeModuleProxy(JSIExecutor &executor) : executor_(executor) {}
// NativeModuleProxy 的 get方法 用于获取native module信息
Value get(Runtime &rt, const PropNameID &name) override {
return executor_.nativeModules_.getModule(rt, name);
}
};
那么NativeModuleProxy这个C++类又是在哪里使用的呢?全局搜索NativeModuleProxy,你会发现只有一个地方再使用NativeModuleProxy,就是JSIExecutor的loadApplicationScript方法,源码如下:
// JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(*this)));
// 此处省略若干行代码...
}
不难看出,上面出镜率最高的代码就是runtime_->global().setProperty( //... ); runtime是一个JSCRuntime类型对象,通过调用rumtime_->global()获得一个全局的global对象。然后又通过setProperty方法给global对象设置了一个名为nativeModuleProxy的对象。日后(JS侧的)global对象通过"nativeModuleProxy"这个名字即可访问到(native侧的)NativeModuleProxy,这听起来像是一句废话。说到这里,我们不得不说一下JS侧的global.nativeModuleProxy,我们会诧异于在native侧和JS侧的global中都存在nativeModuleProxy变量,其实这不是巧合,本质上,JS侧的global.nativeModuleProxy就是native侧的nativeModuleProxy。换句话说,我们在JS侧的NativeModules对应的就是native侧的nativeModuleProxy。JS侧代码如下:
// 源码位置:react-native/Libraries/BatchedBridge/NativeModules.js
let NativeModules: {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
const bridgeConfig = global.__fbBatchedBri
通过上述JS代码可以看出,JS侧的NativeModules == JS侧的global.nativeModuleProxy == native侧NativeModuleProxy。
JS侧对NativeModules的调用都会经由native侧的NativeModuleProxy后进而调用到JSINativeModules的的createModule这个实例方法返回了moduleName对应的module config。值得一提的是,在createModule方法中,还调用了一个名为“__fbGenNativeModule”的JS方法,如下:
// JSINativeModules.cpp
// JSINativeModules::createModule方法中
if (!m_genNativeModuleJS) {
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
通过runtime获取JS全局对象global,接着通过方法名“__fbGenNativeModule”从golbal实例中获取这个JS方法指针,然后进行调用。当然,要想在native侧可以调用到这个JS方法,前提是需要在JS侧对这个方法进行定义。如下是这个方法在JS侧的源码实现:
// 源码位置:react-native/Libraries/BatchedBridge/NativeModules.js
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{name: string, module?: Object} {
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
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);
});
return {name: moduleName, module};
}
// 导出genModule到全局变量global上以便native可以调用
global.__fbGenNativeModule = genModule;
上面JS源码先是定义了一个名为genModule的函数,然后又把这个函数挂载到了全局变量global的"__fbGenNativeModule"上,以便作为一个可以全局访问的全局函数,这样做的目的是方便在native侧调用。所以,__fbGenNativeModule这个函数指代的就是gemModule。
到这里JSIExecutor的初始化完成了,这样和JS之间的桥梁就建好了,以后Native call JS都会先后经由Instance、NativeToJSBridge、JSIExecutor最终到达JS。
下图描述了bridge初始化的方法调用时序图和涉及到的主要类:
RN bridge初始化加载JS代码
然后,我们不要忘了,以上这一大段篇幅只是初始化了RCTCxxBridge中的_reactInstance以及instance背后的NativeToJsBridge、JSIExecutor。我们还记得RCTCxxBridge的start方法中,除了初始化_reactInstance、NativeToJSBridge、JSIExecutor之外,与之同时进行的还有加载JS源码,jsBundle的加载是通过RCTJavaScriptLoader进行的。当初始化工作和JS源码加载都完成后,就会执行JS源码。让我们来回顾一下RCTCxxBridge.mm中的start方法:
// RCTCxxBridge.mm
- (void)start {
// 此处省略若干行...
// 1. 初始化native module
dispatch_group_t prepareBridge = dispatch_group_create();
// 2. 在JS线程初始化_reactInstance、RCTMessageThread、nativeToJsBridge、JSCExecutor
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 3. 异步加载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) {
// 展示加载bundle 的 loadingView
#if RCT_DEV && __has_include(<React/RCTDevLoadingView.h>)
RCTDevLoadingView *loadingView = [weakSelf moduleForName:RCTBridgeModuleNameForClass([RCTDevLoadingView class])
lazilyLoadIfNecessary:NO];
[loadingView updateProgress:progressData];
#endif
}];
// 4. 等待native moudle 和 JS 代码加载完毕后就执行JS
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];
}
});
}
很明显,在初始化_reactInstance完成之后,还有异步load JS源码以及执行源码的工作(实际上,因为dispatch_group_t的原因,初始化_reactInstance和load JS源码是并发执行的,但只有在两者工作都完毕后才去执行JS代码)。本节我们将会介绍JS代码的加载。
// RCTCxxBridge.mm
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
// 发送通知 将要加载JS代码
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge];
// JS代码加载完成的回调
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) {
NSDictionary *userInfo = @{
RCTBridgeDidDownloadScriptNotificationSourceKey: source ?: [NSNull null],
RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey: self->_bridgeDescription ?: [NSNull null],
};
[center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo];
_onSourceLoad(error, source);
};
// 通知delegate
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 {
// 加载JS代码
__weak RCTCxxBridge *weakSelf = self;
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) {
onSourceLoad(error, source);
}];
}
}
可以看出,上述代码中通过调用RCTJavaScriptLoader的类方法loadBundleAtURL:onProgress:onComplete加载JS bundle。如下是源码:
// RCTJavaScriptLoader.mm
+ (void)loadBundleAtURL:(NSURL *)scriptURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)onComplete
{
int64_t sourceLength;
NSError *error;
// 尝试 同步加载JS bundle
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;
// 尝试 异步加载JS bundle
if (isCannotLoadSyncError) {
attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
} else {
onComplete(error, nil);
}
}
不难看出,上面一段代码还是很清晰的,主要做了2件事:
1.尝试同步加载JS bundle,加载成功就执行onComplete回调
2.如果不能同步加载则尝试异步加载JS bundle,否则直接onComplete
那么什么情况下可以同步加载JS bundle?答案是如果要加载的bundle是本地预置的或是已经下载好的,那么就可以同步加载,否则只能异步download。
下面我们分别来看下同步加载bundle和异步加载bundle的实现。
同步加载bundle
// RCTJavaScriptLoader.mm
+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
runtimeBCVersion:(int32_t)runtimeBCVersion
sourceLength:(int64_t *)sourceLength
error:(NSError **)error
{
// 如果bundle不在本地,那么就不能同步加载
if (!scriptURL.fileURL) {
if (error) {
*error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
userInfo:@{NSLocalizedDescriptionKey:
[NSString stringWithFormat:@"Cannot load %@ URLs synchronously",
scriptURL.scheme]}];
}
return nil;
}
// 检查前4个字节来判断这个bundle是普通bundle还是RAM bundle
// 如果是RAM bundle,则在前4个字节有一个数字 `(0xFB0BD1E5)`
// RAM bundle相对于普通bundle的好处是当有需要时再去以“懒加载”的形式2把modules注入到JSC
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
// 关于fread各个参数的解释:
// __ptr -- 这是指向带有最小尺寸 size*nitems 字节的内存块的指针。
// __size -- 这是要读取的每个元素的大小,以字节为单位。
// __nitems -- 这是元素的个数,每个元素的大小为 size 字节。
// __stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
facebook::react::BundleHeader header;
size_t readResult = fread(&header, sizeof(header), 1, bundle);
fclose(bundle);
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:
break;
}
struct stat statInfo;
if (sourceLength) {
*sourceLength = statInfo.st_size;
}
return [NSData dataWithBytes:&header length:sizeof(header)];
}
如上,主要做了3件事:
1.如果bundle不在本地,那么就不能同步加载,写入error
2.检查前4个字节来获取这个bundle类型,类型信息存在ScriptTag中
3.返回bundle data
异步加载bundle
上面介绍了同步加载bundle就是读取本地磁盘预置或预先下载的bundle数据,所以不难判断异步加载bundle就是下载网络上的bundle。下面我们来看下源码:
// RCTJavaScriptLoader.mm
static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoadProgressBlock onProgress, RCTSourceLoadBlock onComplete)
{
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;
}
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;
}
// 验证服务器返回的是不是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 = [NSError errorWithDomain:@"JSServer"
code:NSURLErrorCannotParseResponse
userInfo:@{
NSLocalizedDescriptionKey: description,
@"headers": headers,
@"data": data
}];
onComplete(error, nil);
return;
}
// 把data包装成source对象
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];
}
如上,不难看出,异步加载主要做了2件事情:
1.如果bundle是本地文件则异步加载本地bundle
2.如果不是本地bundle则开启一个RCTMultipartDataTask异步下载
以上,是RN所有加载JS代码的逻辑。接下来介绍native是如何执行JS代码的。
执行JS代码
执行JS代码,终于走到这一步了,还是要从RCTCxxBridge的start方法说起,如下:
// RCTCxxBridge.mm
- (void)start
{
// 此处省略若干行...
// 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];
}
});
}
不难看出,start方法在最后通过调用executeSourceCode:执行JS代码,executeSourceCode:源码如下:
// RCTCxxBridge.mm
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
dispatch_block_t completion = ^{
// 在主线程上执行状态更新和通知,这样我们就不会遇到RCTRootView的时序问题
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidLoadNotification
object:self->_parentBridge userInfo:@{@"bridge": self}];
});
};
// 根据sync来选择执行JS的方式(同步、异步)
if (sync) {
[self executeApplicationScriptSync:sourceCode url:self.bundleURL];
completion();
} else {
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
}
}
如上,代码主要做了两件事:
1.准备一个completion的block,在JS执行完成后回调
2.根据sync来选择是同步执行还是异步执行JS
通过看这两个函数的实现,不难发现,最终他们都是调用了同一个方法,如下:
// RCTCxxBridge.mm
- (void)executeApplicationScript:(NSData *)script
url:(NSURL *)url
async:(BOOL)async
{
[self _tryAndHandleError:^{
NSString *sourceUrlStr = deriveSourceURL(url);
// 发送 将要执行JS 的通知 RCTJavaScriptWillStartExecutingNotification
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartExecutingNotification
object:self->_parentBridge userInfo:@{@"bridge": self}];
// 如果是RAMBundle则调用_reactInstance的loadRAMBundle:方法
// 否则调用_reactInstance的loadScriptFromString:方法
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 (self->_reactInstance) {
auto registry = RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
self->_reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr),
sourceUrlStr.UTF8String, !async);
}
} else if (self->_reactInstance) {
self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
sourceUrlStr.UTF8String, !async);
} else {
std::string methodName = async ? "loadApplicationScript" : "loadApplicationScriptSync";
throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
}
}];
}
如我在以上源码中加的注释所述,这个方法做了2件事:
1.发送一个将要执行JS的通知 名为RCTJavaScriptWillStartExecutingNotification
2.根据bundle的类型(是否为RAMBundle)分别调用_reactInstance的不同方法。如果是RAMBundle则调用_reactInstance的loadRAMBundle:方法,否则调用_reactInstance的loadScriptFromString:方法。因篇幅问题,因篇幅问题,此处不对RAM bundle展开介绍。
// Instance.cpp
// load普通的 JS bundle
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadApplication(nullptr, std::move(string), std::move(sourceURL));
}
}
// load RAM bundle
void Instance::loadRAMBundle(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadApplicationSync(std::move(bundleRegistry), std::move(startupScript),
std::move(startupScriptSourceURL));
} else {
loadApplication(std::move(bundleRegistry), std::move(startupScript),
std::move(startupScriptSourceURL));
}
}
如上,我们发现无论是加载RAM bundle还是加载普通的bundle都调用了Instance的loadApplicationSync或loadApplication方法。下面我们来看这两个方法:
// Instance.cpp
void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string),
std::move(sourceURL));
}
void Instance::loadApplicationSync(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
nativeToJsBridge_->loadApplicationSync(std::move(bundleRegistry), std::move(string),
std::move(sourceURL));
}
如上,我们发现,Instance执行JS的方法又调用到了nativeToJsBridge这一层:
// NativeToJsBridge.cpp
void NativeToJsBridge::loadApplication(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> 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();
// 如果是RAM bundle则把 bundle 传给 executor
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
// 调用JSIExecutor加载脚本
try {
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
void NativeToJsBridge::loadApplicationSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
if (bundleRegistry) {
m_executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
m_executor->loadApplicationScript(std::move(startupScript),
std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
}
如上,loadApplication和loadApplicationSync这两个方法实现基本一致,都是调用了成员变量m_executor的loadApplicationScript方法,区别在于loadApplication把代码放到了m_executorMessageQueueThread中去执行,而loadApplicationSync在当前线程执行。让我们来看下JSIExecutor(生产环境)的loadApplicationScript的实现:
// JSIexecutor.cpp
void JSIExecutor::loadApplicationScript(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(*this)));
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) {
callNativeModules(args[0], false);
return Value::undefined();
}));
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); }));
// 最终调用到JavaScriptCore的JSEvaluateScript函数
runtime_->evaluateJavaScript(
std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
flush();
}
如上,loadApplicationScript方法主要做了3件事:
1.将Native侧的NativeModuleProxy对象注入到global的nativeModuleProxy上,相当于global.nativeModuleProxy = new NativeModuleProxy。
2.然后向global中注入了nativeFlushQueueImmediate,nativeCallSyncHook 2个方法。相当于global.nativeFlushQueueImmediate = JSIExecutor::callNativeModules(args); global.nativeCallSyncHook = JSIExecutor::nativeCallSyncHook(args);注入成功后,JS侧的global调用nativeFlushQueueImmediate或nativeCallSyncHook这两个方法就会直接调用到JSIExecutor对应的方法上。
3.调用runtime_->evaluateJavaScript方法,最终调用到JavaScriptCore的JSEvaluateScript函数。对JavaScriptCore了解的开发者应该都知道JSEvaluateScript的作用就是在JS环境中执行JS代码。
4.JS脚本执行完成,执行flush操作。flush函数的主要作用就是执行JS侧的队列中缓存的对native的方法调用。
evaluateJavaScript
上面说runtime最终调用到了JavaScriptCore的JSEvaluateScript函数。让我们再来看下JSCRuntime的evaluateJavaScript实现:
// JSCRumtime.cpp
jsi::Value JSCRuntime::evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string& sourceURL) {
std::string tmp(
reinterpret_cast<const char*>(buffer->data()), buffer->size());
JSStringRef sourceRef = JSStringCreateWithUTF8CString(tmp.c_str());
JSStringRef sourceURLRef = nullptr;
if (!sourceURL.empty()) {
sourceURLRef = JSStringCreateWithUTF8CString(sourceURL.c_str());
}
JSValueRef exc = nullptr;
JSValueRef res =
JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
return createValue(res);
}
不难看出,最终还是调用的JavaScriptCore的JSEvaluateScript这个函数来执行JS代码。对JavaScriptCore或Hybrid开发有了解的人应该对这个函数非常熟悉,这个函数最关键的是前两个参数ctx和script,分别代表JS执行环境和将要执行的脚本字符串。
flush
在ctx中执行JS源码,会初始化JS环境,BatchedBridge.js,NativeModules.js中的初始化代码也会执行。在BatchedBridge.js中,创建了一个名为BatchedBridge的MessageQueue,并设置到global的__fbBatchedBridge属性里,这个属性后面会用到。在初始化JS环境的时候,会加载到某些NativeModule,这些module才会被初始化,即调用到native侧JSINativeModules的getModule方法。当相关的Module都加载完之后,evaluateScript方法执行完,JS环境初始化完毕。然后就到执行flush方法。如下:
// JSIExecutor.cpp
void JSIExecutor::flush() {
if (flushedQueue_) {
callNativeModules(flushedQueue_->call(*runtime_), true);
return;
}
Value batchedBridge =
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
if (!batchedBridge.isUndefined()) {
bindBridge();
callNativeModules(flushedQueue_->call(*runtime_), true);
} else if (delegate_) {
callNativeModules(nullptr, true);
}
}
上面的逻辑是:
1.如果JSIExecutor的flushedQueue_函数不为空,则通过函数flushedQueue_获取待调用的方法queue,然后执行callNativeModules。(第一次调用flush()时,flushedQueue_必为空,稍后在bindBridge()中才bind flushedQueue_)
2.以"__fbBatchedBridge"作为属性key去global中取对应的值也就是batchedBridge,batchedBridge本质上是JS侧的MessageQueue类实例化的一个对象
3.如果获取到了JS侧定义的batchedBridge对象,则执行bindBridge操作(我们知道在JS初始化环境的时候,JS的batchedBridge这个值已经被初始化为MessageQueue对象,在BatchedBridge.js中,创建了一个名为BatchedBridge的MessageQueue对象,并设置到global的__fbBatchedBridge属性里),即把batchedBridge中的方法和Native侧JSIExecutor的方法进行绑定。这些bind操作本质上是native指针指向JS函数。例如:把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor对象的callFunctionReturnFlushedQueue_进行绑定;把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_进行绑定;把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_进行绑定。把batchedBridge中的callFunctionReturnResultAndFlushedQueue 和 JSIExecutor中的callFunctionReturnResultAndFlushedQueue_进行绑定。bind完成之后,执行callNativeModules方法。
4.如果没有获取到JS侧定义的batchedBridge对象,则直接执行callNativeModules方法,即没有bind操作。
让我们继续看bindBridge方法的实现吧:
// JSIExecutor.cpp
void JSIExecutor::bindBridge() {
std::call_once(bindFlag_, [this] {
Value batchedBridgeValue =
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
*runtime_, "callFunctionReturnFlushedQueue");
invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
*runtime_, "invokeCallbackAndReturnFlushedQueue");
flushedQueue_ =
batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
callFunctionReturnResultAndFlushedQueue_ =
batchedBridge.getPropertyAsFunction(
*runtime_, "callFunctionReturnResultAndFlushedQueue");
});
}
bindBridge把global中的函数变量赋值给了JSIExecutor的成员变量指针,这为后面Native call JS做好了准备。
Native调用JS
上面说了bindBridge方法中把global.batchedBridge中的方法和Native侧JSIExecutor的方法进行绑定。本质上就是Native指针指向JS函数(例如:JSIExecutor::callFunctionReturnFlushedQueue_ = global.batchedBridge.callFunctionReturnFlushedQueue)。这样就可以在native侧直接调用到JS函数,实现native调用JS。本节将从源码的角度介绍Native调用JS的相关细节。
执行完JS源码完成后,在RCTCxxBridge中会发送一个名为RCTJavaScriptDidLoadNotification的通知。如下:
// RCTCxxBridge.mm
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
// 可以在任何执行JS的线程调用
dispatch_block_t completion = ^{
// 在主线程上执行状态更新和通知,这样我们就不会遇到RCTRootView的时序问题
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程发送一个通知
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidLoadNotification
object:self->_parentBridge userInfo:@{@"bridge": self}];
});
};
if (sync) {
[self executeApplicationScriptSync:sourceCode url:self.bundleURL];
completion();
} else {
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
}
}
RCTRootView监听到这个通知后执行了javaScriptDidLoad:方法,然后沿着Instance->NativeToJsBridge->JSIExecutor这个调用链调用了JSIExecutor::callFunction方法,方法内调用了JSIExecutor的callFunctionReturnFlushedQueue_方法,bindBridge一节中介绍了callFunctionReturnFlushedQueue_是通过runtime将native指针指向JS函数。所以,就相当于调用JS MessageQueue的callFunctionReturnFlushedQueue方法,该方法接收调用JS方法所需的moduleId、methodId和参数,执行完毕后JS会给Native返回一个queue,该queue中是一系列JS需要native侧执行的方法。最后这个queue被交给callNativeModules进行调用。详细调用过程如下:
// RCTRootView.m
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _contentView.bridge) {
[self bundleFinishedLoading:bridge];
}
}
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge
reactTag:self.reactTag
sizeFlexiblity:_sizeFlexibility];
[self runApplication:bridge];
[self insertSubview:_contentView atIndex:0];
}
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
// 调用RCTCxxBridge的enqueueJSCall:method:args:completion:方法
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
// NativeToJsBridge.cpp
// 该方法可以在任何线程调用
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^(){
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf->_reactInstance) {
// 调用Instance的callJSFunction方法
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
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方法
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) {
// 调用JSIExecutor的callFunction方法
executor->callFunction(module, method, arguments);
});
}
// JSIExecutor.cpp
void JSIExecutor::callFunction(
const std::string &moduleId,
const std::string &methodId,
const folly::dynamic &arguments) {
Value ret = Value::undefined();
try {
scopedTimeoutInvoker_(
[&] {
// 调用callFunctionReturnFlushedQueue_并把JS需要Native侧执行的方法queue作为返回值返回,赋值给ret
// 传入JS moduleId、methodId、arguements
// 返回值 queue
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侧刷新queue中需要执行的方法
callNativeModules(ret, true);
}
上面介绍了通过MessageQueue的callFunctionReturnFlushedQueue实现Native调用JS。除此之外还有其他3个与Native call JS相关的函数,我们已经在bindBridge中见过了。他们分别是:invokeCallbackAndReturnFlushedQueue、flushedQueue、callFunctionReturnResultAndFlushedQueue。看名字就知道他们的作用,这里不做详细介绍。 接下来JS的MessageQueue.js对callFunctionReturnFlushedQueue的实现:
// MessageQueue.js
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
__callFunction(module: string, method: string, args: any[]): any {
const moduleMethods = this.getCallableModule(module);
const result = moduleMethods[method].apply(moduleMethods, args);
return result;
}
flushedQueue() {
this.__guard(() => {
this.__callImmediates();
});
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
可以看出callFunctionReturnFlushedQueue主要做了两件事:
1.通过moduleId和methodId完成方法的调用(__callFunction函数)
2.返回一个queue(flushedQueue函数)
至此,Native调用JS相关的实现基本介绍完了。让我们总结下:
JS代码执行JS上下文环境都已经初始化,MessagQueue相关代码也会被调用,然后会通过runtime让Native指针指向JS函数,后面Native call JS都是通过这4个函数完成的。JS代码执行完毕后,RCTCxxBridge会发送一个名为RCTJavaScriptDidLoadNotification的通知给RCTRootView。然后经过RCTRootView->RCTBridge->RCTCxxBridge->Instance->NativeToJsBridge->JSIExecutor层层调用,最终通过调用callFunctionReturnFlushedQueue完成Native对JS的调用。Native call JS的四个核心函数如下,至此Native call JS基本介绍完了。
flushedQueue
callFunctionReturnFlushedQueue
invokeCallbackAndReturnFlushedQueue
callFunctionReturnResultAndFlushedQueue
JS调用Native
前面说过,JS中global.__fbGenNativeModule属性其实就是NativeModules.js中定义的genModule函数。在加载JS脚本的时候,将JSINativeModule的m_genNativeModuleJS指向了global.__fbGenNativeModule。即global.__fbGenNativeModule == genModule == m_genNativeModuleJS。在执行JS源码时候,最终会调用到JSIExecutor::loadApplicationScript方法。这个方法中初始化了一个nativeModuleProxy对象并设置给了global.nativeModuleProxy。初始化nativeModuleProxy对象会触发nativeModuleProxy的get方法,get方法最终调用到JSINativeModules的createModule方法,createModule中调用了m_genNativeModuleJS方法即JS侧genModule函数,方法入参是native侧的ModuleConfig对象,返回值是moduleInfo(形如:{modulename, moduleInfo},moduleName是模块名,moduleInfo是这个模块信息,包括模块方法)。JS侧拿到这个返回值进行缓存,后续通过缓存的这个moduleInfo获取native侧的模块配置,进而调用native方法,例如NativeModule.moduleName.methodName。
需要说明的是,通常情况下,JS是不会“直接的”调用OC方法的。当我们在JS中通过NativeModule调用native方法时,模块ID和方法ID会被加入一个名为_queue的队列,等到native侧调用JS方法时,顺便把这个队列作为返回值返回给native侧。Native侧再一一解析队列中的每一个moduleID和methodID后,封装成NSInvocation完成调用。如下是JS调用Native的流程图:
JS call Native 流程解析我们知道MessageQueue.js承接了Native和JS通信的任务。在Native调用JS一节中我们知道了callFunctionReturnFlushedQueue这个函数用于Native call JS,并把JS中的queue返回给Native。这个queue存储了一系列JS对Native的调用。native侧拿到这个queue后,就会解析这个queue中的内容,得到相关模块的配置和参数,并进行动态调用。iOS上的调用主要是把这些配置和参数封装NSInvocation实例,进行调用。其调用顺序大致如下:
// JSIExecutor.app
void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
// delegate_是JsToNativeBridge类型的实例
delegate_->callNativeModules(
*this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
// JsToNativeBridge.cpp
void callNativeModules(
__unused JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
}
// ModuleRegistry.cpp
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
// RCTNativeModule.mm
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) {
__weak RCTBridge *weakBridge = m_bridge;
__weak RCTModuleData *weakModuleData = m_moduleData;
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
}
// RCTNativeModule.mm
static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic ¶ms) {
id<RCTBridgeMethod> method = moduleData.methods[methodId];
NSArray *objcParams = convertFollyDynamicToId(params);
id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
return convertIdToFollyDynamic(result);
}
// RCTModuleMethod.mm
- (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
{
[_invocation invokeWithTarget:module];
}
以上,是JS call Native 的方法调用链,主要是JSIExecutor先收到JS侧的调用请求,然后将请求转发给JSIExecutor实例的m_delegate,也就是调用m_delegate的callNativeModules方法,m_delegate本质上就是一个JSToNativeBridge实例,他是在NativeToJSBridge的初始化列表中初始化JSIExecutor时传给JSIExecutor的。m_delegate继续调用m_registry的callNativeMethod方法,m_registry是m_delegate的一个成员变量,也就是JSToNativeBridge的成员变量。然后调用经由NativeModule和RCTBridgeMethod转发,最后在RCTBridgeMethod中通过NSInvocation的方式完成了native侧方法的调用。
至此,JS调用Native方法就介绍完了。
三个疑问
你可能有3个疑问:
1.为什么JS不同步调用native方法而选择异步?
2.为什么RN不主动调用JS而是把调用缓存到队列,而是等native call JS时再把队列以返回值的形式返回给native?这样JS还能跑的通吗?
3.设计成这样如何保证native侧的方法可以得到及时的调用?
对于第一个问题:我们知道JS代码是运行在JS线程而非main thread,并且JS是单线程,如果同步调用native方法就会block住JS代码的运行,所以RN选择了JS和Native异步通信。
对于第二个问题:JS不会主动传递数据给OC,在调OC方法时,会把ModuleID,MethodID等数据加到一个队列里,等OC过来调JS的任意方法时,再把这个队列返回给OC,此时OC再执行这个队列里要调用的方法。让我们回顾下iOS的事件传递和响应机制就会恍然大悟,在Native开发中,只在有事件触发的时候,才会调用native代码。这个事件可以是启动事件、触摸事件、滚动事件、timer事件、系统事件、回调事件。而在React Native里,本质上JSX Component最终都是native view。这些事件发生时OC都会调用JS侧相应的方法以保证JS侧和native侧保持同步,JS处理完这些事件后再执行JS想让OC执行的方法,而没有事件发生的时候,是不会执行任何代码的,这跟native开发里事件响应机制是一致的。在native调用JS的时候,JS把需要native处理的方法队列返回给native侧让native去处理。这样既保证了JS和native侧事件和逻辑的同步,JS也可以趁机搭车call native,避免了JS和native侧频繁的通信。 RN的这种设计还是很合理的,真的是巧夺天工,这种设计思路还是值得借鉴的。
对于第三个问题:JS只是被动的等待native call JS,然后趁机把队列返回给native,那么如何保证方法调用的及时性呢?换句话说,如果native迟迟不调用JS,那JS侧队列中一大堆方法旧只能干等着吗?答案当然不是这样的,JS规定了一个时间阈值,这阈值是5ms,如果超过5ms后依旧没有native call JS。那么JS就会主动触发队列的刷新,即立即让native侧执行队列中缓存的一系列的方法。JS侧触发native的源码如下:
// MessageQueue.js
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
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();
if (
global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
) {
const queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
}
如上,每次JS想要call native,都会判断距上一次flush queue的时间间隔,如果间隔大于或等于5ms,就调用global.nativeFlushQueueImmediate方法,把queue作为入参传给native。
大结局
好了,虽然讲了很多,也很啰嗦,但事无巨细,尤其是对于RN这种庞大的工程框架,一篇文章是很难覆盖各个细节的。对于一些重要的细节,日后找机会写文章单独分析。最后总结一下RN的初始化启动流程:
RN的启动流程主要分4个阶段:bridge初始化阶段、源码加载阶段、源码执行阶段、运行JSApplication阶段
bridge初始化阶段
bridge初始化阶段主要是初始化并配置了RCTBridge实例、RCTCxxBridge实例、Instance实例、NativeToJSBridge实例、JSIExecutor实例。
- 在APPDelegate的启动方法中创建了RCTBridge和一个RCTRootView,然后在RCTBridge中创建了一个名为batchedBridge的RCTCxxBridge实例,并调用了self.batchedBridge start方法。在start方法里先创建了一个专门用来运行JS代码的JS线程,这个线程绑定到了一个runloop以免退出。然后将所有注册的module生成RCTModuleData,并根据需要调用他们的初始化方法,以上操作都是在main thread中进行的。
- 接下来一边在JS线程中执行Intance的初始化方法,一边异步进行JS源码的加载。Intance的初始化主要是在Instance::initializeBridge方法里创建了一个名为nativeToJsBridge_的NativeToJSBridge实例变量。然后NativeToJsBridge的初始化方法里通过executorFactory创建了一个名为m_executor的JSIExecutor实例并传入了一个runtime变量。至此,Instance、NativeToJSBridge、JSIExecutor初始化完成。
源码加载阶段
start方法中初始化Instance的同时还在load JS源码,JS源码加载分为同步和异步。JS bundle类型分为RAM bundle和普通bundle。load完成后返回JS sourceCode进入源码执行阶段。
源码执行阶段
当JS sourceCode加载完成且native module也初始化也完成后,就会继续走执行JS源码的逻辑,主要是初始化JS上下文环境,并建立Native call JS的能力,即Native指针指向JS函数。
- start方法先是调用executeSourceCode方法,然后经过RCTCxxBridge -> Instance -> NativeToJsBridge -> JSIExecutor层层调用最终会执行到JSIExecutor::loadApplicationScript方法。在JSIExecutor::loadApplicationScript中通过runtime向global中注入了nativeModuleProxy。并且将JSIExecutor::nativeFlushQueueImmediate、JSIExecutor::nativeCallSyncHook注入到global中。
- 向global注入nativeModuleProxy的时候,会触发nativeModuleProxy的get方法,目的是生成native module的配置信息并返回给JS。 主要逻辑是通过nativeModuleProxy -> JSINativeModules.getModule -> JSINativeModules.createModule层层调用,最后调用JS在global中注册的genModule这个方法把native module配置信息生成对应的JS对象,JS会缓存这个对象用于将来调用native。当然native侧早就有了一份module配置信息表,这个表存储在ModuleRegistry中。日后JS call Native 都是传递的模块ID和方法ID,然后native再根据ID去查表完成方法调用。
- 在JSIExecutor::loadApplicationScript中会调用JSCRumtime的evaluteJavaScript方法,进而调用到JavaScriptCore的JSEvaluateScript方法执行JS源码。
- 在JS源码执行完后,JSIExecutor::loadApplicationScript中会调用flush()方法刷新JS的messageQueue中缓存的方法队列。
运行JS Application阶段
上面JS脚本执行完后,RCTCxxBridge会发送一个名为RCTJavaScriptDidLoadNotification的通知给RCTRootView执行runApplication相关的逻辑。RCTRootView收到通知后会把运行JS所需的moduleName、methodName、appKey和页面所需的参数传给JS。moduleName通常是@"AppRegistry",用于调用RN的名字为"AppRegistry"的组件。methodName通常是@"runApplication",用于调用JS的AppRegistry.runApplication方法。appKey是页面的key,也是AppRegistry.runApplication的入参,用于唯一标识一个组件,本文中是@"NewProject"。参数很好理解了,就是初始化页面的一些业务参数。
- 执行runApplication相关的逻辑链条:RCTRootView -> RCTBridge->RCTCxxBridge->Instace->NativeToJsBridge->JSIExecutor。通过以上层层调用后,最终调用到JSIExecutor的callFunction方法,如果没有绑定JS和Native侧的方法则调用bindBridge执行bind逻辑,将MessageQueue.js中定义的4个方法绑定到JSIExecutor的成员变量上(相当于Native指针指向JS函数),这4个函数是flushQueue、callFunctionReturnFlushedQueue、invokeCallbackAndReturnFlushedQueue、callFunctionReturnResultAndFlushedQueue。关于bind这4个函数的逻辑在源码执行阶段已经讲过了。
- 然后调用callFunctionReturnFlushedQueue方法获取JS待native刷新的方法队列,把队列通过JSIExecutor::callNativeModules交给JSIExecutor的m_delegate进行处理。m_delegate是一个JSToNativeBridge的实例。专门负责处理JS call Native相关的逻辑。
- 第2步中调用callFunctionReturnFlushedQueue时会把@"AppRegistry"、@"runApplication"、appKey、arguments作为入参传递个JS,JS侧收到这个消息后,就调用了JS中的AppRegistry的runApplication方法。到这里,JS的入口就被调用,界面就会渲染出来。JS的入口如下:
总结下来,React Native用iOS自带的JavaScriptCore作为JS的解析引擎,即JS和Native的相互通信是经过JavaScriptCore机制来进行的。但并没有用到JavaScriptCore提供的一些可以让JS与OC互调的特性,而是自己实现了一套机制,这套机制可以通用于所有JS引擎上。
在程序启动阶段会收集所有native暴露给JS的模块和方法。然后初始化阶段会创建并配置JS相关的桥。在执行JS源码阶段,Native的JSIExecutor会注入nativeModuleProxy和两个方法到JS全局变量global中,相当于JS指针指向Native变量,这样就建立了JS调用Native的能力。注入到global中的两个方法是nativeFlushQueueImmediate和nativeCallSyncHook,将来JS侧调用这两个方法就会走到Native侧的方法实现中。
但是JS call Native并不直接调用Native的方法,而且先放入一个JS队列中,每次Native call JS时JS都会将这个队列返回给Native,Native拿到这个队列后根据队列中的moduleID、methodID和方法参数等信息生成NSInvocation,完成Native的方法调用。这种实现方式避免了JS和Native的频繁通信。
JS脚本加载并执行完成后native会调用JS,告诉JS可以runApplication了,接下页面救护被渲染出来。
至此,全篇完!
文/VV木公子(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载请联系作者获得授权,并注明出处。
如果您是iOS开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!
网友评论