該系列文章希望從非 UI 模塊以及 UI 模塊的初始化與通信來剖析 React Native 一些實現(xiàn)原理。這篇文章是該系列的第一篇凿宾,主要分析了非 UI 模塊從 APP 啟動矾屯、Native 端的模塊定義到 JavaScript 端的模塊生成過程(基于 React Native @0.29.0 iOS 實現(xiàn))。
時序圖總覽
先來看下整體的時序圖初厚,后面會依次介紹件蚕。
[Native] iOS main 函數(shù)執(zhí)行
首先,iOS APP 啟動后先執(zhí)行的是 main 函數(shù)产禾,此處不多說排作。
[Native] 注冊 Native Module(在各個 Native Module 的定義中)
在 Native 端實現(xiàn)模塊時都需要調用 RCT_EXPORT_MODULE
來聲明該模塊需要暴露給 JavaScript。除此之外還有一批宏定義用來聲明其他的信息:
- RCT_EXPORT_MODULE:聲明模塊
- RCT_EXPORT_METHOD:聲明方法
- RCT_EXPORT_VIEW_PROPERTY:聲明屬性
通過這種方式完成模塊定義后亚情,就可以非常方便的獲取到所有需要對外暴露的模塊以及模塊需要對外暴露的方法與屬性妄痪。
以下是核心的宏定義:
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
#define RCT_EXPORT_VIEW_PROPERTY(name, type) \
+ (NSArray<NSString *> *)propConfig_##name { return @[@#type]; }
...
[Native] 收集各個模塊的 config
通過上一步完成所有模塊的定義后,RCTBatchedBridge
中會把所有模塊信息集中收集起來楞件,并且按照固定的格式重新組織衫生,最后會把這些模塊信息序列化后注入到 JavaScript 中。
核心代碼如下:
// RCTBatchedBridge.m
- (NSString *)moduleConfig
{
NSMutableArray<NSArray *> *config = [NSMutableArray new];
for (RCTModuleData *moduleData in _moduleDataByID) {
if (self.executorClass == [RCTJSCExecutor class]) {
[config addObject:@[moduleData.name]];
} else {
[config addObject:RCTNullIfNil(moduleData.config)];
}
}
return RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
}
@RCTModuleData
- (NSArray *)config
{
[self gatherConstants];
__block NSDictionary<NSString *, id> *constants = _constantsToExport;
_constantsToExport = nil; // Not needed anymore
if (constants.count == 0 && self.methods.count == 0) {
return (id)kCFNull; // Nothing to export
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
NSMutableArray<NSString *> *methods = self.methods.count ? [NSMutableArray new] : nil;
NSMutableArray<NSNumber *> *asyncMethods = nil;
for (id<RCTBridgeMethod> method in self.methods) {
if (method.functionType == RCTFunctionTypePromise) {
if (!asyncMethods) {
asyncMethods = [NSMutableArray new];
}
[asyncMethods addObject:@(methods.count)];
}
[methods addObject:method.JSMethodName];
}
NSMutableArray *config = [NSMutableArray new];
[config addObject:self.name];
if (constants.count) {
[config addObject:constants];
}
if (methods) {
[config addObject:methods];
if (asyncMethods) {
[config addObject:asyncMethods];
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
return config;
}
[Native] 初始化 Bridge(在 ViewController 中)
self.rctRootView =
[[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"TiebaNext"
initialProperties:nil
launchOptions:nil];
[Native] 將模塊信息注入到 JSCExecutor 中的全局變量
// RCTBatchedBridge.m
- (void)injectJSONConfiguration:(NSString *)configJSON
onComplete:(void (^)(NSError *))onComplete
{
if (!_valid) {
return;
}
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:onComplete];
}
__fbBatchedBridgeConfig
的結構如下
{
"remoteModuleConfig": [
[
"模塊名稱",
{
屬性對象(鍵值對土浸,值類型不限)
},
[
方法列表
],
[
Promise 方法 index(方法列表中哪些方法是異步的)
]
],
注
:
- 每個模塊的 index 就是這個模塊的 moduleID
- 每個方法的 index 就是這個方法的 methodID
- 最后輸出到 remoteModuleConfig 中的模塊并不是所有的 Module罪针,有的會被過濾掉,所以輸出里有的是 null
以上結構的生成在 RCTModuleData config 方法中:
// RCTModuleData.m
- (NSArray *)config
{
//......
NSMutableArray *config = [NSMutableArray new];
[config addObject:self.name]; // 模塊名稱(字符串)
if (constants.count) {
[config addObject:constants]; // 屬性對象(對象)
}
if (methods) {
[config addObject:methods]; // 方法列表(數(shù)組)
if (asyncMethods) {
[config addObject:asyncMethods]; // Promise 方法列表(數(shù)組)
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
return config;
}
舉個例子
{
"remoteModuleConfig": [
null,
[
"RCTAccessibilityManager",
[
"setAccessibilityContentSizeMultipliers",
"getMultiplier",
"getCurrentVoiceOverState"
]
],
[
"RCTViewManager",
{
"forceTouchAvailable": false
}
],
...
[
"RCTClipboard",
[
"setString",
"getString"
],
[
1
]
],
...
[
"RCTPushNotificationManager",
{
"initialNotification": null
},
[
"setApplicationIconBadgeNumber",
"getApplicationIconBadgeNumber",
"requestPermissions",
"abandonPermissions",
"checkPermissions",
"presentLocalNotification",
"scheduleLocalNotification",
"cancelAllLocalNotifications",
"cancelLocalNotifications",
"getInitialNotification",
"getScheduledLocalNotifications",
"addListener",
"removeListeners"
],
[
2,
9
]
]
[JS] 根據(jù)上述模塊配置信息生成 JS 模塊
//BatchedBridge.js
const BatchedBridge = new MessageQueue(
() => global.__fbBatchedBridgeConfig
);
注
:
- BatchedBridge 是 MessageQueue 的一個實例
- 第一次訪問 BatchedBridge.RemoteModules 時 MessageQueue 會利用 global.__fbBatchedBridgeConfig 模塊配置信息來生成 JS 模塊
[JS] JS 模塊的生成
首先栅迄,JS 模塊的結構
{
name: "模塊名稱",
{
方法名:方法實現(xiàn)(區(qū)分是否 Promise(Sync)) 類型
屬性名:
moduleID: 模塊Index
}
}
具體包括幾塊:
- 模塊名
- 模塊屬性
- moduleID
- 模塊方法
第一步 遍歷所有 remoteModules站故,即 global.__fbBatchedBridgeConfig 列表:
// MessageQueue.js
_genModules(remoteModules) {
const modules = {};
remoteModules.forEach((config, moduleID) => {
const info = this._genModule(config, moduleID);
if (info) {
modules[info.name] = info.module;
}
});
return modules;
}
第二步 解析出每個 Module 的信息皆怕,包括模塊名毅舆、屬性對象西篓、方法列表、異步(Promise)方法列表憋活、syncHooks(這個和 native 結構對應不上岂津,有可能棄用了)
// MessageQueue.js
let moduleName, constants, methods, asyncMethods, syncHooks;
if (moduleHasConstants(config)) {
[moduleName, constants, methods, asyncMethods, syncHooks] = config;
} else {
[moduleName, methods, asyncMethods, syncHooks] = config;
}
第三步 根據(jù)方法列表、屬性對象生成 module 對象
// MessageQueue.js
// 初始化新 module
const module = {};
methods && methods.forEach((methodName, methodID) => {
const isAsync = asyncMethods && arrayContains(asyncMethods, methodID);
const isSyncHook = syncHooks && arrayContains(syncHooks, methodID);
invariant(!isAsync || !isSyncHook, 'Cannot have a method that is both async and a sync hook');
// 方法類型悦即,Async 方法調用會返回 Promise 對象
const methodType = isAsync ? MethodTypes.remoteAsync :
isSyncHook ? MethodTypes.syncHook :
MethodTypes.remote;
// 生成方法
module[methodName] = this._genMethod(moduleID, methodID, methodType);
});
// 將屬性淺拷貝到 module 上
Object.assign(module, constants);
關于方法的生成:
- 實際上都是將方法的實際操作代理給了 __nativeCall(moduleID, methodID, args, onFail, onSucc)
- 對于 async 方法會用 Promise 對象包裝一下吮成,然后 onFail 的時候 reject,以及 onSucc 的時候 resolve(但是看代碼里onFail 和 onSucc 的位置不一致辜梳,是否有問題需要進一步求證)
- 關于 __nativeCall 的邏輯后續(xù)寫一篇JS代碼執(zhí)行后的分析來剖析
// MessageQueue.js
_genMethod(module, method, type) {
let fn = null;
const self = this;
if (type === MethodTypes.remoteAsync) {
fn = function(...args) {
return new Promise((resolve, reject) => {
self.__nativeCall(
module,
method,
args,
(data) => {
resolve(data);
},
(errorData) => {
var error = createErrorFromErrorData(errorData);
reject(error);
});
});
};
} else if (type === MethodTypes.syncHook) {
return function(...args) {
return global.nativeCallSyncHook(module, method, args);
};
} else {
fn = function(...args) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccCB = typeof lastArg === 'function';
const hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(
hasSuccCB,
'Cannot have a non-function arg after a function arg.'
);
const numCBs = hasSuccCB + hasErrorCB;
const onSucc = hasSuccCB ? lastArg : null;
const onFail = hasErrorCB ? secondLastArg : null;
args = args.slice(0, args.length - numCBs);
return self.__nativeCall(module, method, args, onFail, onSucc);
};
}
fn.type = type;
return fn;
}
[JS] NativeModules 生成
經(jīng)過前面的步驟粱甫,native 注入到 JSCExecutor 中的 global.__fbBatchedBridgeConfig 在 BatchedBridge.RemoteModules 屬性首次被訪問的時候被解析成完整的 module,下一步則是將這些 module 放入 NativeModules 對象并供外部訪問作瞄。
另外茶宵,這里有個一個小處理,native 注入的模塊通常以 RCT 或者 RK 為前綴宗挥,此處會將前綴干掉(以下代碼未包含)乌庶。
//NativeModules.js
const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
//代碼省略...
/**
* 這里也是一個 lazy getters
* Define lazy getters for each module.
* These will return the module if already loaded, or load it if not.
*/
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
Object.defineProperty(NativeModules, moduleName, {
configurable: true,
enumerable: true,
get: () => {
let module = RemoteModules[moduleName];
//代碼省略...
Object.defineProperty(NativeModules, moduleName, {
configurable: true,
enumerable: true,
value: module,
});
return module;
},
});
});
[JS] UIManager 的再加工
native 生成 __fbBatchedBridgeConfig
配置信息的時候,view 模塊和非 view 模塊(區(qū)別是 view 模塊都是由 RCTViewManager 來管理)是分別對待的契耿,所有的 view 模塊信息都放在了 "RCTUIManager" 的屬性對象中瞒大,參照 __fbBatchedBridgeConfig
的結構大概是下面這樣的。
通過 NativeModules 的生成過程完成了 remoteModuleConfig 中外層模塊的處理(包括 NativeModules.UIManager 也在這一過程中生成了初始版本)搪桂,那 UIManager 的再加工過程則是解析和處理 RCTUIManager 內各種 view 的過程透敌。
{
"remoteModuleConfig": [
[
// 模塊名稱
"RCTUIManager",
// 屬性對象
{
// 此處省略
"RCTRawText": {
"Manager": "RCTRawTextManager",
"NativeProps": {
"text": "NSString"
}
},
"RCTSwitch": {
"Manager": "RCTSwitchManager",
"NativeProps": {
"thumbTintColor": "UIColor",
"tintColor": "UIColor",
"value": "BOOL",
"onChange": "BOOL",
"onTintColor": "UIColor",
"disabled": "BOOL"
}
}
// 此處省略
},
// 方法列表
[
"removeSubviewsFromContainerWithID",
"removeRootView",
"replaceExistingNonRootView",
"setChildren",
"manageChildren",
"createView",
"updateView",
"focus",
"blur",
"findSubviewIn",
"dispatchViewManagerCommand",
"measure",
"measureInWindow",
"measureLayout",
"measureLayoutRelativeToParent",
"measureViewsInRect",
"takeSnapshot",
"setJSResponder",
"clearJSResponder",
"configureNextLayoutAnimation"
],
// Promise 方法 index(方法列表中哪些方法是異步的)
[
16
]
],
UIManager 的再加工最主要包括兩個方面,為 UIManager 上的每個 view 添加 Constants 和 Commands 屬性锅棕。
-
1 生成 Constants 屬性
- 獲取每個 view 的 Manager 名稱拙泽,例如
RCTSwitchManager
- 在 NativeModules 上查找
RCTSwitchManager
對象 - 如果存在則將
RCTSwitchManager
對象中所有非 function 屬性放入 Constants
- 獲取每個 view 的 Manager 名稱拙泽,例如
// UIManager.js
const viewConfig = UIManager[viewName];
if (viewConfig.Manager) {
let constants;
/* $FlowFixMe - nice try. Flow doesn't like getters */
Object.defineProperty(viewConfig, 'Constants', {
configurable: true,
enumerable: true,
get: () => {
if (constants) {
return constants;
}
constants = {};
const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
viewManager && Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value !== 'function') {
constants[key] = value;
}
});
return constants;
},
});
-
2 生成 Commands 屬性
- 獲取每個 view 的 Manager 名稱,例如
RCTSwitchManager
- 在 NativeModules 上查找
RCTSwitchManager
對象 - 如果存在則將
RCTSwitchManager
對象中所有 function 屬性放入 Commands裸燎,value 為屬性的索引值
- 獲取每個 view 的 Manager 名稱,例如
// UIManager.js
let commands;
/* $FlowFixMe - nice try. Flow doesn't like getters */
Object.defineProperty(viewConfig, 'Commands', {
configurable: true,
enumerable: true,
get: () => {
if (commands) {
return commands;
}
commands = {};
const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
let index = 0;
viewManager && Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value === 'function') {
commands[key] = index++;
}
});
return commands;
},
});
啟動 JavaScript App
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
[bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
}