致敬 开始准备写这一篇文章的时候,中国的新型冠状病毒肺炎疫情还在继续,累积确诊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是本文涉及到的最内层的类):
开始 我们新建一个名为NewProject的RN的iOS工程。可以看出AppDelegate.m的application: didFinishLaunchingWithOptions:方法实现是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (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的初始化的源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 // 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源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 // 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: 的方法,如下:
1 2 3 4 5 6 // RCTCxxBridge.mm [self ensureOnJavaScriptThread:^{ [weakSelf _initializeBridge:executorFactory]; dispatch_group_leave(prepareBridge); }];
看名字就知道,ensureOnJavaScriptThread:是RCTCxxBridge里专门将block放在JS线程中执行的方法。他的目的就是确保待执行的block能在JS线程执行,所以他接收一个block作为参数,然后判断当前线程是否是先前创建的JS线程,如果是则立即在JS线程同步执行block,否则切换到JS线程执行block。源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 // 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:到底做了哪些事情,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 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。
1 2 3 4 5 6 7 8 9 // 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,我们不做详细介绍。
1 2 3 4 5 6 7 8 9 10 11 12 13 // 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实例并返回,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 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的实例对象。通过如下代码可窥见一斑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // 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实例持有。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 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定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 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个重要的成员变量:
m_delegate是JsToNativeBridge类型的引用,主要用于JS call Native
JSExecutor类型引用,主要用于执行Native call JS,实际上生产环境使用的是JSIExecutor;调试环境使用的是RCTObjcExecutor
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。
std::unique_ptr bundleRegistry, std::unique_ptr startupCode, std::string sourceURL);
这个方法的作用是执行JS代码,他还有一个兄弟叫做loadApplicationSync,顾名思义,他兄弟是一个同步函数,所以他自己就是异步执行JS代码。
NativeToJsBridge构造函数 上面说了NativeBridge的3个关键属性和4个关键方法,接下来我们说下他的构造函数,接触过C++的开发者应该知道,C++中类的构造函数和类同名,如下是NativeToJsBridge的构造函数:
1 2 3 4 5 6 7 8 9 10 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生产,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 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主要的几个关键属性:
Runtime类型指针,代表JS的运行时。这是一个抽象类,其实际上是由JSCRuntime来实现的。JSCRuntime实现了jsi::Runtime 接口,提供了创建JS上下文的功能,同时可以执行JS。如下是JSCRuntime的evaluateJavaScript方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 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类型的指针,这里的ExecutorDelegate是抽象类,实际是由JsToNativeBridge实现的。也即JSIExecutor引用了JsToNativeBridge实例。还记得NativeToJsBridge中的JsToNativeBridge类型的成员变量m_delegate吗?其实这里的delegate_就是NativeToJsBridge中的m_delegate。
JSINativeModules由上层传入的ModuleRegistry构造而成,同时会将ModuleRegistry中包含的本地模块配置信息通过”__fbGenNativeModule”保存到JS端。
JSINativeModules有个getModule方法,getModule方法内又调用了 createModule方法,createModule方法生成了module信息,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // 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方法里调用的,如下:
1 2 3 4 5 6 7 8 9 10 11 12 // 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方法,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 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侧代码如下:
1 2 3 4 5 6 7 // 源码位置: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方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 // 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侧的源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 源码位置: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初始化的方法调用时序图和涉及到的主要类:
加载JS代码 然后,我们不要忘了,以上这一大段篇幅只是初始化了RCTCxxBridge中的_reactInstance以及instance背后的NativeToJsBridge、JSIExecutor。我们还记得RCTCxxBridge的start方法中,除了初始化_reactInstance、NativeToJSBridge、JSIExecutor之外,与之同时进行的还有加载JS源码,jsBundle的加载是通过RCTJavaScriptLoader进行的。当初始化工作和JS源码加载都完成后,就会执行JS源码。让我们来回顾一下RCTCxxBridge.mm中的start方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // 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代码的加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // 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。如下是源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 // 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。下面我们来看下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 // 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方法说起,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 // 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:源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 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
通过看这两个函数的实现,不难发现,最终他们都是调用了同一个方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // 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展开介绍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // 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方法。下面我们来看这两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 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这一层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // 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的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 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实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 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方法。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 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方法的实现吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 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的通知。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 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进行调用。详细调用过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 // 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的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 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基本介绍完了。
1 2 3 4 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的流程图:
我们知道MessageQueue.js承接了Native和JS通信的任务。在Native调用JS一节中我们知道了callFunctionReturnFlushedQueue这个函数用于Native call JS,并把JS中的queue返回给Native。这个queue存储了一系列JS对Native的调用。native侧拿到这个queue后,就会解析这个queue中的内容,得到相关模块的配置和参数,并进行动态调用。iOS上的调用主要是把这些配置和参数封装NSInvocation实例,进行调用。其调用顺序大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // 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的源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // 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开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!