该系列文章希望从非 UI 模块以及 UI 模块的初始化与通信来剖析 React Native 一些实现原理。这篇文章是该系列的第一篇,主要分析了非 UI 模块从 APP 启动、Native 端的模块定义到 JavaScript 端的模块生成过程(基于 React Native @0.29.0 iOS 实现)。
时序图总览
先来看下整体的时序图,后面会依次介绍。
image[Native] iOS main 函数执行
首先,iOS APP 启动后先执行的是 main 函数,此处不多说。
[Native] 注册 Native Module(在各个 Native Module 的定义中)
在 Native 端实现模块时都需要调用 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]; // 方法列表(数组)
if (asyncMethods) {
[config addObject:asyncMethods]; // Promise 方法列表(数组)
}
}
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] 根据上述模块配置信息生成 JS 模块
//BatchedBridge.js
const BatchedBridge = new MessageQueue(
() => global.__fbBatchedBridgeConfig
);
注
:
- BatchedBridge 是 MessageQueue 的一个实例
- 第一次访问 BatchedBridge.RemoteModules 时 MessageQueue 会利用 global.__fbBatchedBridgeConfig 模块配置信息来生成 JS 模块
[JS] JS 模块的生成
首先,JS 模块的结构
{
name: "模块名称",
{
方法名:方法实现(区分是否 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;
}
第三步 根据方法列表、属性对象生成 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 的逻辑后续写一篇JS代码执行后的分析来剖析
// 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 生成
经过前面的步骤,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 模块(区别是 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]];
}
网友评论