? React Native(以下簡稱RN)的目標是用基于react的JavaScript寫代碼,在iOS/Android平臺上原生渲染,正如他們的口號"Learn Once,Write anywhere!",只要學會了大前端,iOS/Android/Web通吃,這樣就很神奇了,react.js還是那個react.js,模塊化、虛擬DOM、JSX語法概念一樣沒少,甚至可以基于流行的flux單向數據流來架構我們的應用,而在客戶端并不是一個web頁面,而是純原生渲染,性能比純web頁提升很多,而且還順帶具有像web頁一樣的動態更新能力。
? 隨著版本的迭代更新,RN功能和相關特性也越來越多,代碼復雜度也隨之上升,這里先不論RN的爭議和發展趨勢,而來學習下其優秀的架構設計和代碼實現。我們就用一個最簡單的項目來進行剖析,運行命令react-native init RNDemo
,這里我的RN版本號為:0.47.0,RN最核心的當屬js與native的通信機制,理解了這套機制則比較容易理解RN整個架構。
Native模塊
RCTBridgeModule
native導出給js的類稱之為模塊類,如RCTUIManager
RCTTiming
等,每個模塊類都實現了RCTBridgeModule
協議
@protocol RCTBridgeModule <NSObject>
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;
@optional
@property (nonatomic, weak, readonly) RCTBridge *bridge;
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
#define RCT_EXPORT_METHOD(method) \
RCT_REMAP_METHOD(, method)
#define RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(method) \
RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(, method)
#define RCT_REMAP_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
- (void)method;
#define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \
- (id)method;
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
#define RCT_EXTERN_METHOD(method) \
_RCT_EXTERN_REMAP_METHOD(, method, NO)
#define RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(method) \
_RCT_EXTERN_REMAP_METHOD(, method, YES)
#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+ (NSArray *)RCT_CONCAT(__rct_export__, \
RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
}
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
- (NSDictionary<NSString *, id> *)constantsToExport;
- (void)batchDidComplete;
- (void)partialBatchDidFlush;
@end
該協議定義了模塊導出方法、導出常量、模塊運行隊列等,還定義了很多宏。
RCT_EXPORT_MODULE
模塊類被加載進runtime的時候,執行load
方法,將該模塊類的Class
信息添加到一個全局數組RCTModuleClasses
里去,RCTGetModuleClasses()
方法可獲取該數組。
RCT_EXPORT_METHOD(method)
RN在e9095b2
版本移除了bang神博客所說的從data數據段獲取導出方法的黑魔法,而是給每個導出方法添加一個對應的方法。
比如method為doSomething
,則宏展開后為
+ (NSArray *)__rct_export__(行號和系統計數){
return @[@"", @"doSomething", @(NO)];
}
- (void)doSomething;
RCTModuleData
方法- (NSArray<id<RCTBridgeMethod>> *)methods
,通過遍歷模塊運行時方法列表,找到有__rct_export__
前綴的方法,根據方法返回的數組實例化RCTModuleMethod
,從而收集到所有RCT_EXPORT_METHOD
對應的導出方法。
模塊配置
所有的模塊配置存放在ModuleRegistry
C++類中,JSCExecutor
的getNativeModule
方法可獲得指定模塊的配置,最終是從ModuleRegistry
類方法getConfig
拿到。JSCNativeModules
管理Native的導出模塊,JSCNativeModules
構造函數傳入JsToNativeBridge
的getModuleRegistry
方法返回的ModuleRegistry
指針,JsToNativeBridge
管理js調用Native所需配置、方法等,是js調用native的native響應方。
以RCTAppState
模塊為例,拿到的模塊信息如下:
struct ModuleConfig {
size_t index;
folly::dynamic config;
};
{
21;
[AppState,{initialAppState:unknown},[getCurrentAppState,addListener,removeListeners]];
}
index是模塊index,config動態數組依次存放moudlename、export constants、export methodNames array 、promiseMethodId array 、syncMethodId array。
Native模塊生成
Native模塊初始化
在應用啟動delegate里,創建了一個RCTRootView
實例,這個實例初始化了RCTBridge
實例,在其- (void)setUp
方法里:
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
self.batchedBridge
是RCTCxxBridge
的實例,用于批量橋接,之前版本的RCTBatchedBridge
已不再實現,在- (void)start
方法主要步驟是:
- 發送js即將加載通知
-
創建常駐線程
_jsThread
,native和js互相調用默認會在該線程執行,也可以自己指定模塊的運行隊列_jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(runJSRunLoop) object:nil]; _jsThread.name = RCTJSThreadName; _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; [_jsThread start];
-
初始化所有native modules
- (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup { ... NSArray<id<RCTBridgeModule>> *extraModules = nil; if (self.delegate) { if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { extraModules = [self.delegate extraModulesForBridge:_parentBridge]; } } else if (self.moduleProvider) { extraModules = self.moduleProvider(); } ... NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new]; NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new]; NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new]; ... // Set up moduleData for pre-initialized module instances for (id<RCTBridgeModule> module in extraModules) { Class moduleClass = [module class]; NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); if (RCT_DEBUG) { ... } // Instantiate moduleData container RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } ... // Set up moduleData for automatically-exported modules for (Class moduleClass in RCTGetModuleClasses()) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); if ([moduleName isEqual:@"RCTJSCExecutor"]) { continue; } // Check for module name collisions RCTModuleData *moduleData = moduleDataByName[moduleName]; if (moduleData) { if (moduleData.hasInstance) { // Existing module was preregistered, so it takes precedence continue; } else if ([moduleClass new] == nil) { // The new module returned nil from init, so use the old module continue; } else if ([moduleData.moduleClass new] != nil) { // Both modules were non-nil, so it's unclear which should take precedence RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the " "name '%@', but name was already registered by class %@", moduleClass, moduleName, moduleData.moduleClass); } } // Instantiate moduleData // TODO #13258411: can we defer this until config generation? moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } ... // Store modules _moduleDataByID = [moduleDataByID copy]; _moduleDataByName = [moduleDataByName copy]; _moduleClassesByID = [moduleClassesByID copy]; ... // Dispatch module init onto main thead for those modules that require it for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) { (void)[moduleData instance]; } } ... // From this point on, RCTDidInitializeModuleNotification notifications will // be sent the first time a module is accessed. _moduleSetupComplete = YES; [self _prepareModulesWithDispatchGroup:dispatchGroup]; ... }
主要步驟:
-
得到bridgeModule數組
extraModules
從delegate實現方或傳入的
moduleProvider
屬性獲取extraModules
,這里兩者都為空 實例化
moduleClassesByID
Class
數組,moduleDataByID
RCTModuleData*
模塊數據數組,moduleDataByName
名字模塊數據字典,臨時保存,作用見名思義遍歷
extraModules
,填充第二步數組和字典-
從
RCTGetModuleClasses()
得到聲明了RCT_EXPORT_MODULE
的所有模塊Class
,遍歷數組,先檢查命名沖突,再以moduleClass
為參數實例化RCTModuleData
,然后填充第二步數組和字典RCTModuleData
類管理導出給js的模塊數據,包括Class
信息,導出方法,導出常量等 遍歷
_moduleDataByID
,調用RCTModuleData
對應實例的instance
方法,初始化RCTModuleData
類執行
_prepareModulesWithDispatchGroup
方法,初始化除白名單外的模塊導出常量
-
-
實例化
Instance
類,該類在下文有介紹_reactInstance.reset(new Instance);
-
實例化抽象工廠類
JSExecutorFactory
__weak RCTCxxBridge *weakSelf = self; std::shared_ptr<JSExecutorFactory> executorFactory; if (!self.executorClass) { BOOL useCustomJSC = [self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] && [self.delegate shouldBridgeUseCustomJSC:self]; // The arg is a cache dir. It's not used with standard JSC. executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object ("UseCustomJSC", (bool)useCustomJSC) #if RCT_PROFILE ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch) #endif )); } else { id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass]; executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { if (error) { [weakSelf handleError:error]; } })); }
本地JavaScriptCore運行會實例化
JSCExecutorFactory
, 瀏覽器遠程調試模式會實例RCTObjcExecutorFactory
,這里我們就以JSCExecutorFactory
為例分析,遠程調試會在另一篇做分析。 -
在
_jsThread
線程上初始化橋接- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory { if (!self.valid) { return; } RCTAssertJSThread(); __weak RCTCxxBridge *weakSelf = self; _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) { if (error) { [weakSelf handleError:error]; } }); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil); // This can only be false if the bridge was invalidated before startup completed if (_reactInstance) { // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance _reactInstance->initializeBridge( std::unique_ptr<RCTInstanceCallback>(new RCTInstanceCallback(self)), executorFactory, _jsMessageThread, [self _buildModuleRegistry]); #if RCT_PROFILE ... #endif } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); }
主要步驟:
- 實例化
RCTMessageThread
類,RCTMessageThread
類封裝了在_jsThread常駐線程上的同步和異步執行任務的方法。 -
Instance
實例_reactInstance
執行了初始化方法void initializeBridge(..)
, 注入了所需依賴,Instance
是iOS/Android與javacriptCore交互的入口類。初始化了模塊注冊表ModuleRegistry
實例,ModuleRegistry
是C++類,ios/android均需填充模塊信息數組
- 實例化
-
加載jsbundle源文件
RCTJavaScriptLoader
封裝了加載jsbundle文件的方法,主要步驟是需要下載則通過RCTMultipartDataTask
下載js文件,最后返回NSData
數據 -
執行解析jsbundle
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]; } });
在上述組任務結束時,收到通知,在最高等級全局隊列執行加載完的jsbundle
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync { // This will get called from whatever thread was actually executing JS. dispatch_block_t completion = ^{ // Flush pending calls immediately so we preserve ordering [self _flushPendingCalls]; // Perform the state update and notification on the main thread, so we can't run into // timing issues with RCTRootView dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; // Starting the display link is not critical to startup, so do it last [self ensureOnJavaScriptThread:^{ // Register the display link to start sending js calls after everything is setup [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; }]; }); }; if (sync) { [self executeApplicationScriptSync:sourceCode url:self.bundleURL]; completion(); } else { [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion]; } #if RCT_DEV ... #endif }
主要步驟:
清空調用隊列
_pendingCalls
,_pendingCount
置0發送js加載完畢通知,
_jsThread
線程,設置RCTDisplayLink
監測js線程幀率,原理在另一篇作分析-
執行解析jsbundle,這里是異步執行
執行解析中間過程為了解耦和擴展性,引入了
NativeToJsBridge
等類,層次很多,可能看到這就有點暈了,來看下這塊的執行過程。
jsBundle執行
首先,上文提到的JSCExecutorFactory
,運用了標準的工廠模式:
JSCExecutor
是本地JavaScriptCore具體執行的產品類,是跨平臺的C++類,是一個非常重要的類。
JSCExecutor
構造時向JSContext注入了全局函數
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
...
installGlobalProxy(m_context, "nativeModuleProxy",
exceptionWrapMethod<&JSCExecutor::getNativeModule>());
...
installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
installNativeHook
方法同時注冊了回調函數exceptionWrapMethod<method>()
,當JS端調用對應方法,exceptionWrapMethod
全局函數被調用。
? MessageQueueThread
是RCTMessageThread
的抽象基類,封裝了在消息隊列里同步和異步執行的方法,MessageQueueThread
是需要各平臺各自實現的。
class MessageQueueThread {
public:
virtual ~MessageQueueThread() {}
virtual void runOnQueue(std::function<void()>&&) = 0;
// runOnQueueSync and quitSynchronous are dangerous. They should only be
// used for initialization and cleanup.
virtual void runOnQueueSync(std::function<void()>&&) = 0;
// Once quitSynchronous() returns, no further work should run on the queue.
virtual void quitSynchronous() = 0;
};
Instance
類依賴了很多類,它是一個C++類,是iOS/Android與javacriptCore交互的入口類,封裝了native與js的交互,包括解析js字符流,調用js方法,設置jscontext全局變量,發出回調等。
class RN_EXPORT Instance {
public:
~Instance();
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry);
void setSourceURL(std::string sourceURL);
void loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously);
void loadUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously);
bool supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
void callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params);
void callJSCallback(uint64_t callbackId, folly::dynamic&& params);
// This method is experimental, and may be modified or removed.
template <typename T>
Value callFunctionSync(const std::string& module, const std::string& method, T&& args) {
CHECK(nativeToJsBridge_);
return nativeToJsBridge_->callFunctionSync(module, method, std::forward<T>(args));
}
#ifdef WITH_JSC_MEMORY_PRESSURE
void handleMemoryPressure(int pressureLevel);
#endif
private:
void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
void loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
void loadApplicationSync(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
};
Instance
類依賴的InstanceCallback
JSExecutorFactory
MessageQueueThread
ModuleRegistry
等類,都需要平臺各自實現,Instance
是作為一個跨平臺的接口封裝類,如iOS中,callback
成員變量是指向InstanceCallback
的子類RCTInstanceCallback
,jsef
成員則動態指向JSExecutorFactory
的具體工廠類,JSExecutorFactory
則可以選擇對應的具體產品類。jsQueue
指向RCTMessageThread
類,moduleRegistry
需要各平臺自行填充。Instance
類函數實現基本都是通過NativeToJsBridge
具體實現的,在Instance
執行initializeBridge
時,在MessageQueueThread
同步初始化了NativeToJsBridge
的實例nativeToJsBridge_,那么我們來看看NativeToJsBridge
類。
? 在RCTCxxBridge
類的enqueueApplicationScript:url:onComplete:
方法,根據jsbundle類型去執行對應的方法。jsbundle目前有三種類型:String
RAMBundle
BCBundle
,String
表示普通jsbundle,用bundle
命令整合出來的。RAMBundle
是用unbundle
命令打出來的bundle,它除了生成整合的js文件index.ios.bundle
外,還會生成各個單獨的未整合js文件,全部放在js-modules
目錄下, bundle頭四個字節固定為0xFB0BD1E5
。BCBundle
是js字節碼bundle類型,并未用到,就以普通jsbundle為例
self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
sourceUrlStr.UTF8String, false);
核心是通過Instance
實例去執行解析, Instance
的loadScriptFromString
方法調用到NativeToJsBridge
的loadApplication
和loadApplicationSync
的方法,NativeToJsBridge
實例在Instance
類構造的時候在_jsThread
線程初始化。
void NativeToJsBridge::loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto unbundle = unbundleWrap.move();
if (unbundle) {
executor->setJSModulesUnbundle(std::move(unbundle));
}
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
普通jsbundle調用到JSExecutor
子類loadApplicationScript
方法,以子類JSCExecutor
為例:
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
SystraceSection s("JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);
std::string scriptName = simpleBasename(sourceURL);
ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
String jsSourceURL(m_context, sourceURL.c_str());
// TODO t15069155: reduce the number of overrides here
#ifdef WITH_FBJSCEXTENSIONS
...
#elif defined(__APPLE__)
BundleHeader header;
memcpy(&header, script->c_str(), std::min(script->size(), sizeof(BundleHeader)));
auto scriptTag = parseTypeFromHeader(header);
if (scriptTag == ScriptTag::BCBundle) {
using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
file_ptr source(fopen(sourceURL.c_str(), "r"), fclose);
int sourceFD = fileno(source.get());
JSValueRef jsError;
JSValueRef result = JSC_JSEvaluateBytecodeBundle(m_context, NULL, sourceFD, jsSourceURL, &jsError);
if (result == nullptr) {
throw JSException(m_context, jsError, jsSourceURL);
}
} else
#endif
{
String jsScript;
{
SystraceSection s_("JSCExecutor::loadApplicationScript-createExpectingAscii");
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
jsScript = adoptString(std::move(script));
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
}
#ifdef WITH_FBSYSTRACE
fbsystrace_end_section(TRACE_TAG_REACT_CXX_BRIDGE);
#endif
SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
evaluateScript(m_context, jsScript, jsSourceURL);
}
flush();
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
}
該方法主要步驟:
-
根據JSBundle header得到JSBundle類型來執行對應的js上下文。
evaluateScript
是由jshelps
組件封裝的,最終調用到JavaScriptCore的方法JSEvaluateScript
。jschelps
主要封裝了JavaScriptCore的相關函數,以及JSStringRef
的C++對象封裝String
類,JSObjectRef
的C++對象封裝Object
類,JSValueRef
的C++對象封裝Value
類等。 -
調用
flush()
native調用js
flushedQueue
方法,返回js端待調用方法隊列,然后native執行,清空該隊列
至此,Native模塊生成和相關準備工作完成,模塊配置存放在ModuleRegistry
類中。
JS模塊
本地jscore運行時,當js需要調用到native模塊的時候,通過nativeModuleProxy
執行native所注入方法,返回對應的模塊信息,而當遠程調試模式時,native向global.__fbBatchedBridgeConfig
注入了所有模塊列表信息,同樣是由native端生成的,如下:
type ModuleConfig = [
string, /* name */
?Object, /* constants */
Array<string>, /* functions */
Array<number>, /* promise method IDs */
Array<number>, /* sync method IDs */
];
在JS中同樣也存在供native調用的模塊,node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js
中,模塊保存在_lazyCallableModules
中。
JS模塊生成
registerCallableModule(name: string, module: Object) {
this._lazyCallableModules[name] = () => module;
}
registerLazyCallableModule(name: string, factory: void => Object) {
let module: Object;
let getValue: ?(void => Object) = factory;
this._lazyCallableModules[name] = () => {
if (getValue) {
module = getValue();
getValue = null;
}
return module;
};
}
通過外部注冊,填充_lazyCallableModules
數組,
Native 調用 JS
在RN里,封裝了底層細節,外部暴露出的是通過RCTCxxBridge
方法enqueueJSCall:method:args:completion
調用,如native向js發送時間消息的方法sendEventWithName:body
實現就是調用該方法。該方法實現如下:
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
if (!self.valid) {
return;
}
/**
* AnyThread
*/
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil);
RCTProfileBeginFlowEvent();
[self _runAfterLoad:^{
RCTProfileEndFlowEvent();
if (self->_reactInstance) {
self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
[RCTConvert folly_dynamic:args ?: @[]]);
// ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
// the block is invoked after callJSFunction
if (completion) {
if (self->_jsMessageThread) {
self->_jsMessageThread->runOnQueue(completion);
} else {
RCTLogWarn(@"Can't invoke completion without messageThread");
}
}
}
}];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
主要通過調用Instance
類的callsJSFunction
方法,最終調用到JSCExecutor::callFunction
方法
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
SystraceSection s("JSCExecutor::callFunction");
// This weird pattern is because Value is not default constructible.
// The lambda is inlined, so there's no overhead.
auto result = [&] {
try {
if (!m_callFunctionReturnResultAndFlushedQueueJS) {
bindBridge();
}
return m_callFunctionReturnFlushedQueueJS->callAsFunction({
Value(m_context, String::createExpectingAscii(m_context, moduleId)),
Value(m_context, String::createExpectingAscii(m_context, methodId)),
Value::fromDynamic(m_context, std::move(arguments))
});
} catch (...) {
std::throw_with_nested(
std::runtime_error("Error calling " + moduleId + "." + methodId));
}
}();
callNativeModules(std::move(result));
}
callFunction
方法先執行js端方法callFunctionReturnFlushedQueue
(在MessageQueue.js文件中),返回js端消息隊列,然后native解析隊列,即調用callNativeModules
,這個過程在下文JS調用Native
有分析。
總體來說還是使用JSCHelpers
中封裝的C++方法evaluateScript(JSContextRef, JSStringRef, JSStringRef)
,在常駐線程來執行js語句,返回結果native解析。
JS調用Native
在node_modules/react-native/Libraries/BatchedBridge/NativeModules.js
文件中:
let NativeModules : {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else {
...
}
module.exports = NativeModules;
本地JavascriptCore執行時,nativeModuleProxy
全局函數在JSCExecutor
構造時,通過installGlobalProxy
方法注入了,這里的else
分支是瀏覽器遠程調試走的。當取nativeModuleProxy
屬性,如執行const RCTAppState = NativeModules.AppState;
,JSObjectGetPropertyCallback
回調在C++端被觸發,調用到JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)
方法,該方法通過JSCNativeModules
的getModule
方法拿到native對應配置,如第一節?模塊配置中拿到對應的配置表。
? js端也有 有BatchedBridge
概念,node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js
中,const BatchedBridge = new MessageQueue();
,BatchedBridge
對象實際上是MessageQueue
的實例,轉到當前目錄下的MessageQueue.js
文件。
? js需要調用native方法的時候,調用enqueueNativeCall
函數,比如js端執行方法:
UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload)
這段代碼是在ReactNativeStack-dev.js
中,用于js端通告native創建視圖,UIManager
實際是NativeModules
對象,本地JavacriptCore運行時,NativeModules
對象方法在native的JSCExecutor::getNativeModule
方法中通過調用js方法global.__fbGenNativeModule
建立,global.__fbGenNativeModule
即指向genModule
方法對象,genModule
方法中調用genMethod
,genMethod
中持有閉包,將native方法調用通過BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
方法加入隊列處理,故上述方法調用最終通過enqueueNativeCall
調用。
enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
if (onFail || onSucc) {
if (__DEV__) {
...
}
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
// to indicate fail (0) or success (1)
onFail && params.push(this._callID << 1);
onSucc && params.push((this._callID << 1) | 1);
this._successCallbacks[this._callID] = onSucc;
this._failureCallbacks[this._callID] = onFail;
}
if (__DEV__) {
...
}
this._callID++;
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
if (__DEV__) {
...
}
this._queue[PARAMS].push(params);
const now = new Date().getTime();
if (global.nativeFlushQueueImmediate &&
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
this._inCall === 0)) {
var queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && this.__spy && isFinite(moduleID)) {
...
} else if (this.__spy) {
this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
}
}
? enqueueNativeCall
向_queue
依次插入moduleID
methodID
params
,flushedQueue
方法會把當前的_callID
插入到_queue
最后,緊接著判斷相鄰兩次flushQueue
時間超過MIN_TIME_BETWEEN_FLUSHES_MS
即5ms,或者當前沒有正在處理的方法,則執行全局nativeFlushQueueImmediate
函數。nativeFlushQueueImmediate
函數傳入_queue
參數,它在native端之前通過installNativeHook
注入了,js端調用后native端收到函數回調,最終對應執行JSCExecutor
類的nativeFlushQueueImmediate
方法,該方法最終調用到JsToNativeBridge
的callNativeModules
方法,callNativeModules
解析出js透傳的參數_queue
,然后動態調用方法。
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
m_registry是ModuleRegistry
的實例
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
invoke
方法即以反射去動態執行方法,具體執行方法各平臺各自實現,iOS上實際執行的類是RCTNativeModule
,它的invoke
方法在參數對應的module
指定線程隊列執行invokeInner
方法,然后經過轉換參數等操作,最終調用到RCTModuleMethod
類的invokeWithBridge:module:arguments
方法,通過NSInvocation
的invokeWithTarget
方法實現動態調用,并返回調用結果,中間經過了處理method name, methodSignature等過程,此處代碼可瀏覽RCTModuleMethod
的類實現。另外在processMethodSignature
方法中,將cbID
和返回結果暫存,調用成功通過JSCExecutor
的m_invokeCallbackAndReturnFlushedQueueJS
屬性 ,調用到js里MessageQueue
類的invokeCallbackAndReturnFlushedQueue
方法,js端拿到返回值,js調用native的閉環形成。
? 那么還有一個問題,js只是把消息加入了隊列,js什么時候去讓native去取js的消息隊列處理?
-
js端超時機制
需要注意的是,遠程調試模式并沒有超時機制,
global.nativeFlushQueueImmediate
始終是 undefined的。每次消息入隊的時候,會檢查距離上次隊列清空完成是否超過5ms,超過則調用
nativeFlushQueueImmediate
清空隊列,native注冊回調被調用,否則立即入隊,由于js是單線程的,5ms內也不會積壓很多消息,所以不用擔心處理效率問題。 -
native主動調用
native調用js方法,native調用
enqueueJSCall:method:args:completion
方法會取到js消息隊列,其實包含folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional<Object> m_callFunctionReturnFlushedQueueJS; folly::Optional<Object> m_flushedQueueJS; folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
處理方法都會返回js消息隊列,即native每次調用js,都會主動去取js隊列,比如事件消息、timer等。
綜上所述,js調用native實際上是有兩種機制的:
- native向jscontext的 global注入全局對象,同時注冊相應的回調,如
nativeFlushQueueImmediate
,js函數被調用,對應native回調被響應 - js組成消息隊列,native調用
flushedQueue
主動去取
第一種是JSPatch
所采用的,不過它注冊的回調是一個block, 第二種機制是最復雜的,對于模塊,需要兩端維護一份配置表,但是最高效的,js方需要執行native方法,僅需傳遞moduleId
methodId
arguments
必要參數給native,而方法真正執行是在native方異步執行的,返回結果異步返回給js方,如果換成方式1,native方法在jscontext同步執行,明顯影響效率,而且 當短時間內有很多條消息,JS并不會去頻繁調用native,會在5ms內去累積消息,然后發送給native。