>

构建跨平台的原生应用,通讯及消息循环代码剖

- 编辑:至尊游戏网站 -

构建跨平台的原生应用,通讯及消息循环代码剖

采取 JS 营造跨平台的原生应用:React Native iOS 通讯机制初探

2015/12/30 · JavaScript · React Native

初藳出处: 天猫商城前端团队(FED)- 乾秋   

图片 1

在初识 React Native 时,特别令人郁结的叁个地点正是 JS 和 Native 五个端之间是什么样相互通讯的。本篇文章对 iOS 端 React Native 运营时的调用流程做下简要总计,以此眼线其背后的通讯机制。

肥皂V 2016 1.3

JS 运营进程

React Native 的 iOS 端代码是一向从 Xcode IDE 里运转的。在运维时,首先要对代码举办编写翻译,不出意外,在编写翻译后会弹出八个指令行窗口,那么些窗口正是通过 Node.js 运营的 development server

主题素材是其一命令行是怎么运营起来的呢?实际上,Xcode 在 Build Phase 的末段七个等第对此做了陈设:
图片 2

故而,代码编写翻译后,就能够实行 packager/react-native-xcode.sh 那几个剧本。
查阅那些本子中的内容,开采它最重若是读取 XCode 带过来的景况变量,相同的时候加载 nvm 包使得 Node.js 际遇可用,最终奉行 react-native-cli 的吩咐:

react-native bundle --entry-file index.ios.js --platform ios --dev $DEV --bundle-output "$DEST/main.jsbundle" --assets-dest "$DEST"

1
2
3
4
5
6
react-native bundle
  --entry-file index.ios.js
  --platform ios
  --dev $DEV
  --bundle-output "$DEST/main.jsbundle"
  --assets-dest "$DEST"

react-native 命令是全局安装的,在自家本机上它的地点是 /usr/local/bin/react-native。查看该文件,它调用了 react-native 包里的local-cli/cli.js 中的 run 方法,最后步入了 private-cli/src/bundle/buildBundle.js。它的调用进程为:

  1. ReactPackager.createClientFor
  2. client.buildBundle
  3. processBundle
  4. saveBundleAndMap

地点四步成功的是 buildBundle 的效用,细节相当多很复杂。总体来讲,buildBundle 的功力周边于 browerify 或 webpack :

  1. 从入口文件开头深入分析模块之间的依赖关系;
  2. 对 JS 文件转载,举个例子 JSX 语法的中间转播等;
  3. 把转化后的次第模块一齐联合为一个 bundle.js

故此 React Native 单独去贯彻这些包裹的经过,并不是向来利用 webpack ,是因为它对模块的分析和编译做了繁多优化,大大升级了打包的过程,那样能够确定保障在 liveReload 时客商及时获取响应。

Tips: 通过拜访 能够见见内部存款和储蓄器中缓存的保有编写翻译后的文本名及文件内容,如:
图片 3

React Native 已经生产近一年岁月了,近些日子也在切磋iOS下用js写app的框架,从徘徊和犹疑中,最后依旧选定React Native,她就好像模模糊糊的靓妞同样,想要下决心追到,但是不便于。要想把她接纳的已存在的有自然体积的app中,更是无庸置疑,让笔者先把她里外都精通精通,在享受一下合理使用到现成app的方案,那是深切React Native体系的第蒸蒸日上篇,后续会延续享受应用进度中的一些认知。

Native 运转进度

Native 放正是贰个 iOS 程序,程序入口是 main 函数,像普通同样,它肩负对应用程序做开头化。

除了 main 函数之外,AppDelegate 也是多个非常重大的类,它首要用来做一些大局的操纵。在应用程序运营之后,此中的 didFinishLaunchingWithOptions 方法会被调用,在这一个方式中,主要做了几件事:

  • 概念了 JS 代码所在的职位,它在 dev 情形下是一个 UOdysseyL,通过 development server 访谈;在生育条件下则从磁盘读取,当然前提是早已手动生成过了 bundle 文件;
  • 开创了三个 RCTRootView 对象,该类承接于 UIView,处于程序有所 View 的最外层;
  • 调用 RCTRootView 的 initWithBundleURL 方法。在该方法中,创设了 bridge 对象。从名称想到所包括的意义,bridge 起着八个端之间的桥接作用,此中的确行事的是类正是盛名的 RCTBatchedBridge

RCTBatchedBridge 是起初化时通讯的主导,大家任重先生而道远关切的是 start 方法。在 start 方法中,会创设一个 GCD 线程,该线程通过串行队列调节了以下多少个至关心体贴要的天职。

本篇详细深入分析下React Native 中 Native和JS的相互调用的规律解析。在此之前bang的篇章已经介绍过,本文从代码层面越来越尖锐的来上课,深入分析基于 React Native 0.17.0 版本, 昂CoraN在便捷升高,当中的剧情已和前面包车型大巴旧版本不怎么差别

loadSource

该义务担任加载 JS 代码到内部存储器中。和前边风流倜傥致,假使 JS 地址是 U安德拉L 的样式,就经过网络去读取,若是是文件的花样,则通过读本地球磁性盘文件的点子读取。

作为初篇,先创建贰个演示工程,以往的分享都是那几个工程为根基。前段时间以此工程还很简单,main.js的任课能够下载这里的代码

initModules

该职务会扫描全部的 Native 模块,提收取要暴光给 JS 的这个模块,然后保留到多个字典对象中。
贰个 Native 模块如若想要暴露给 JS,必要在表明时体现地调用 RCT_EXPORT_MODULE。它的定义如下:

#define RCT_EXPORT_MODULE(js_name) RCT_EXTERN void RCTRegisterModule(Class); + (NSString *)moduleName { return @#js_name; } + (void)load { RCTRegisterModule(self); }

1
2
3
4
#define RCT_EXPORT_MODULE(js_name)
RCT_EXTERN void RCTRegisterModule(Class);
+ (NSString *)moduleName { return @#js_name; }
+ (void)load { RCTRegisterModule(self); }

能够旁观,那正是八个宏,定义了 load 方法,该方法会自动被调用,在议程中对脚下类进行挂号。

模块假若要暴流露钦赐的办法,供给经过 RCT_EXPORT_METHOD 宏举办宣示,原理类似。

GitHub MGReactNativeTest工程里有一贯更动main.jsbundle

setupExecutor

那边设置的是 JS 引擎,同样分为调节和测量检验意况和生育条件:
在调度遭受下,对应的 Executor 为 RCTWebSocketExecutor,它通过 WebSocket 连接收 Chrome 中,在 Chrome 里运营 JS;
在生养境遇下,对应的 Executor 为 RCTContextExecutor,那应该正是风传中的 javascriptcore

演示工程的代码

 render: function() { return ( <View style={styles.container}> <Text style={styles.welcome}> Welcome to React Native! </Text> <Text style={styles.instructions}> {this.state.changeText} </Text> <Text style={styles.welcome} onPress={this._onPress}> Change </Text> </View> ); },

图片 411.png

)

moduleConfig

据悉保存的模块音讯,组装成一个 JSON ,对应的字段为 remoteModuleConfig。

Native 与 JS的相互调用

0.17本子的React Native JS引擎已经全副用到的是iOS自带的JavaScriptCore,在JSContext提供的Native与js相互调用的底子上,封装出了投机的互调方法。下边是一张结构图

图片 5架构图.png

injectJSONConfiguration

该任务将上三个职务创设的 JSON 注入到 Executor 中。
上面是多少个 JSON 示例,由于实在的对象太大,这里只截取了前头的旭日初升部分:
图片 6
JSON 里面正是具有暴流露来的模块音信。

App运行进度中 Native和JS相互调用的日记

[Log] N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}]) (main.js, line 638)[Log] N->JS : RCTDeviceEventEmitter.emit(["networkStatusDidChange",{"network_info":"wifi"}]) (main.js, line 638)[Log] N->JS : AppRegistry.runApplication(["MGReactNative",{"rootTag":1,"initialProps":{}}]) (main.js, line 638)[Log] Running application "MGReactNative" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF (main.js, line 638)[Log] JS->N : RCTUIManager.createView([2,"RCTView",1,{"flex":1}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([3,"RCTView",1,{"flex":1}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([4,"RCTView",1,{"flex":1,"justifyContent":"center","alignItems":"center","backgroundColor":4294311167}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([5,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([6,"RCTRawText",1,{"text":"Welcome to React Native!"}]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([5,null,null,[6],[0],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([7,"RCTText",1,{"textAlign":"center","color":4281545523,"marginBottom":5,"accessible":true,"allowFontScaling":true}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([8,"RCTRawText",1,{"text":"soap1"}]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([7,null,null,[8],[0],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([9,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true,"isHighlighted":false}]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([10,"RCTRawText",1,{"text":"Change"}]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([9,null,null,[10],[0],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([4,null,null,[5,7,9],[0,1,2],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([3,null,null,[4],[0],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.createView([12,"RCTView",1,{"position":"absolute"}]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([2,null,null,[3,12],[0,1],null]) (main.js, line 638)[Log] JS->N : RCTUIManager.manageChildren([1,null,null,[2],[0],null]) (main.js, line 638)

日志显示了开行React Native 界面Native与JS的调用进度,我们从最简便的例子动手,稳步脱下美人的面罩。

executeSourceCode

该职责中会实践加载过来的 JS 代码,推行时传出在此之前注入的 JSON。
在调节和测量检验形式下,会由此 WebSocket 给 Chrome 发送一条 message,内容大要为:

JavaScript

{ id = 10305; inject = {remoteJSONConfig...}; method = executeApplicationScript; url = ""; }

1
2
3
4
5
6
{
    id = 10305;
    inject = {remoteJSONConfig...};
    method = executeApplicationScript;
    url = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true";
}

JS 选拔新闻后,实行打包后的代码。要是是非调节和测验格局,则一贯通过 javascriptcore 的虚构碰着去推行有关代码,效果类似。

Native调用JS (Native->JS)

能够看看,运行上马过后,Native调用了JS的 RCTDevice伊夫ntEmitter.emit 广播了四个事件 appStateDidChange,networkStatusDidChange随后调用 AppRegistry.runApplication(["MGReactNative",{"rootTag":1,"initialProps":{}}]) 运维了React Native引擎。上边大家一小点分析,是黄金时代旦从Native调用到JS的函数AppRegistry.runApplication的

JSContext *context = [[JSContext alloc] init];[context evaluateScript:@"function add { return a + b; }"];JSValue *add = context[@"add"];NSLog(@"Func: %@", add); JSValue *sum = [add callWithArguments:@[@]];NSLog(@"Sum: %d",[sum toInt32]);//OutPut:// Func: function add { return a + b; }// Sum: 28

JSContext 是运营 JavaScript 代码的条件。贰个 JSContext 是贰个大局情形的实例,大家得以从 JSContext全局变量中用下标的艺术抽取JS代码中定义的函数 add,它用JSValue类型包装了三个 JS 函数, 倘使你分明JSValue是三个JS函数类型,能够应用callWithArguments 来调用它。更详细的介绍能够学学那篇作品 Java​Script​Core

聪慧的你势必想到,React Native 的也是用同黄金年代措施调用到AppRegistry.runApplication,是的,不过是因而八个通用接口来调用的RCTJavaScriptContext 封装了OC方法callFunction,

- callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete{ // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];}

_executeJSCall 施行的实际代码是

 method = @“callFunctionReturnFlushedQueue” JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge"); JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef); JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, 

能够见见Native 从JSContext中拿出JS全局对象 __fbBatchedBridge,然后调用了其callFunctionReturnFlushedQueue函数

JS 调用 Native

前面大家看看, Native 调用 JS 是通过发送音信到 Chrome 触发实践、可能直接通过 javascriptcore 实施 JS 代码的。而对于 JS 调用 Native 的事态,又是怎么着的啊?

在 JS 端调用 Native 日常都以直接通过援引模块名,然后就采纳了,例如:

JavaScript

var RCTAlertManager = require('NativeModules').AlertManager

1
var RCTAlertManager = require('NativeModules').AlertManager

看得出,NativeModules 是负有地点模块的操作接口,找到它的概念为:

JavaScript

var NativeModules = require('BatchedBridge').RemoteModules;

1
var NativeModules = require('BatchedBridge').RemoteModules;

而BatchedBridge中是叁个MessageQueue的对象:

JavaScript

let BatchedBridge = new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, __fbBatchedBridgeConfig.localModulesConfig, );

1
2
3
4
let BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

在 MessageQueue 实例中,都有贰个 RemoteModules 字段。在 MessageQueue 的构造函数中得以见见,RemoteModules 正是 __fbBatchedBridgeConfig.remoteModuleConfig 稍微加工后的结果。

JavaScript

class MessageQueue { constructor(remoteModules, localModules, customRequire) { this.RemoteModules = {}; this._genModules(remoteModules); ... } }

1
2
3
4
5
6
7
8
class MessageQueue {
 
  constructor(remoteModules, localModules, customRequire) {
    this.RemoteModules = {};
    this._genModules(remoteModules);
    ...
    }
}

为此难点就改成: __fbBatchedBridgeConfig.remoteModuleConfig 是在哪个地方赋值的?

实质上,那个值正是 从 Native 端传过来的JSON 。如前所述,Executor 会把模块配置组装的 JSON 保存到此中:

JavaScript

[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:onComplete];

1
2
3
[_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];

configJSON 实际保存的字段为:_injectedObjects['__fbBatchedBridgeConfig']

在 Native 第四回调用 JS 时,_injectedObjects 会作为传递消息的 inject 字段。
JS 端收到那个消息,经过上边那些根本的管理进度:

JavaScript

'executeApplicationScript': function(message, sendReply) { for (var key in message.inject) { self[key] = JSON.parse(message.inject[key]); } importScripts(message.url); sendReply(); },

1
2
3
4
5
6
7
'executeApplicationScript': function(message, sendReply) {
    for (var key in message.inject) {
      self[key] = JSON.parse(message.inject[key]);
    }
    importScripts(message.url);
    sendReply();
  },

看见没,这里读取了 inject 字段并展开了赋值。self 是八个大局的命名空间,在浏览器里 self===window

进而,上面代码施行过后,window.__fbBatchedBridgeConfig 就被赋值为了传过来的 JSON 反种类化后的值。

总之:
NativeModules = __fbBatchedBridgeConfig.remoteModuleConfig = JSON.parse(message.inject[‘__fbBatchedBridgeConfig’]) = 模块暴表露的有所音讯

好,有了上述的前提之后,接下去以二个事实上调用例子表明下 JS 调用 Native 的进程。
率先大家透过 JS 调用一个 Native 的形式:

JavaScript

'executeApplicationScript': function(message, sendReply) { for (var key in message.inject) { self[key] = JSON.parse(message.inject[key]); } importScripts(message.url); sendReply(); },

1
2
3
4
5
6
7
'executeApplicationScript': function(message, sendReply) {
    for (var key in message.inject) {
      self[key] = JSON.parse(message.inject[key]);
    }
    importScripts(message.url);
    sendReply();
  },

抱有 Native 方法调用时都会先步入到上边包车型客车秘诀中:

JavaScript

fn = function(...args) { let lastArg = args.length > 0 ? args[args.length - 1] : null; let secondLastArg = args.length > 1 ? args[args.length - 2] : null; let hasSuccCB = typeof lastArg === 'function'; let hasErrorCB = typeof secondLastArg === 'function'; let numCBs = hasSuccCB + hasErrorCB; let onSucc = hasSuccCB ? lastArg : null; let onFail = hasErrorCB ? secondLastArg : null; args = args.slice(0, args.length - numCBs); return self.__nativeCall(module, method, args, onFail, onSucc); };

1
2
3
4
5
6
7
8
9
10
11
fn = function(...args) {
  let lastArg = args.length > 0 ? args[args.length - 1] : null;
  let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
  let hasSuccCB = typeof lastArg === 'function';
  let hasErrorCB = typeof secondLastArg === 'function';
  let numCBs = hasSuccCB + hasErrorCB;
  let onSucc = hasSuccCB ? lastArg : null;
  let onFail = hasErrorCB ? secondLastArg : null;
  args = args.slice(0, args.length - numCBs);
  return self.__nativeCall(module, method, args, onFail, onSucc);
};

也便是倒数后三个参数是不当和不错的回调,剩下的是措施调用自身的参数。

在 __nativeCall 方法中,会将多少个回调压到 callback 数组中,同不经常候把 (模块、方法、参数) 也单身保存到里面包车型地铁行列数组中:

JavaScript

onFail && params.push(this._callbackID); this._callbacks[this._callbackID++] = onFail; onSucc && params.push(this._callbackID); this._callbacks[this._callbackID++] = onSucc; this._queue[0].push(module); this._queue[1].push(method); this._queue[2].push(params);

1
2
3
4
5
6
7
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
this._queue[0].push(module);
this._queue[1].push(method);
this._queue[2].push(params);

到这一步,JS 端告后生可畏段落。接下来是 Native 端,在调用 JS 时,经过如下的流程:

图片 7

简单来讲,便是在调用 JS 时,顺便把以前封存的 queue 用作重返值 黄金年代并赶回,然后会对该重返值进行分析。
在 _handleRequestNumber 方法中,终于实现了 Native 方法的调用:

- (BOOL)_handleRequestNumber:(NSUInteger)i moduleID:(NSUInteger)moduleID methodID:(NSUInteger)methodID params:(NSArray *)params { // 解析模块和办法 RCTModuleData *moduleData = _moduleDataByID[moduleID]; id<RCTBridgeMethod> method = moduleData.methods[methodID]; <a href='; { // 达成调用 [method invokeWithBridge:self module:moduleData.instance arguments:params]; } <a href='; (NSException *exception) { } NSMutableDictionary *args = [method.profileArgs mutableCopy]; [args setValue:method.JSMethodName forKey:@"method"]; [args setValue:RCTJSONStringify(RCTNullIfNil(params), NULL) forKey:@"args"]; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (BOOL)_handleRequestNumber:(NSUInteger)i
                    moduleID:(NSUInteger)moduleID
                    methodID:(NSUInteger)methodID
                      params:(NSArray *)params
{
  // 解析模块和方法
  RCTModuleData *moduleData = _moduleDataByID[moduleID];
  id<RCTBridgeMethod> method = moduleData.methods[methodID];
  <a href='http://www.jobbole.com/members/xyz937134366'>@try</a> {
    // 完成调用
    [method invokeWithBridge:self module:moduleData.instance arguments:params];
  }
  <a href='http://www.jobbole.com/members/wx895846013'>@catch</a> (NSException *exception) {
  }
 
  NSMutableDictionary *args = [method.profileArgs mutableCopy];
  [args setValue:method.JSMethodName forKey:@"method"];
  [args setValue:RCTJSONStringify(RCTNullIfNil(params), NULL) forKey:@"args"];
}

再者,实行后还可能会透过 invokeCallbackAndReturnFlushedQueue 触发 JS 端的回调。具体细节在 RCTModuleMethod 的 processMethodSignature 方法中。

再总括一下,JS 调用 Native 的历程为 :

  • JS 把(调用模块、调用方法、调用参数) 保存到行列中;
  • Native 调用 JS 时,顺便把队列重回过来;
  • Native 处理队列中的参数,同样分析出(模块、方法、参数),并由此NSInvocation 动态调用;
  • Native方法调用完成后,再一次主动调用 JS。JS 端通过 callbackID,找到呼应JS端的 callback,举办贰遍调用

全总过程差没多少正是这么,剩下的八个难题不怕,为何要等待 Native 调用 JS 时才会接触,中间会不会有相当长延时?
实际上,只要有事件触发,Native 就能够调用 JS。举个例子,客商假使对显示屏进行触摸,就能够接触在 RCTRootView 中注册的 Handler,并发送给JS:

JavaScript

[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches" args:@[eventName, reactTouches, changedIndexes]];

1
2
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
                  args:@[eventName, reactTouches, changedIndexes]];

而外触摸事件,还应该有 Timer 事件,系统事件等,只要事件触发了,JS 调用时就能够把队列重回。这块通晓能够参照 React Native通讯机制详解 一文中的“事件响应”后生可畏节。

是时候克制心中的恐怖,开首掀裙子了 ,来拜会main.jsbundle中的JS代码

上文Natvie调用JS的门径到了 __fbBatchedBridge.callFunctionReturnFlushedQueue js代码这一步,demo工程中,大家温馨写的index.ios.js 只有区区几行,去Node Server转意气风发圈或React Native的预编译之后,竟然发出了1.3M,近5W行JS代码的main.jsbundle 文件,对于极端同学来讲,大致是豆蔻梢头座恒山。不要惊恐,大家一同索求当中的奥密。

一而再追踪js代码中的 __fbBatchedBridge

//main.js__d('BatchedBridge',function(global, require, module, exports) { 'use strict'; var MessageQueue=require('MessageQueue'); var BatchedBridge=new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, __fbBatchedBridgeConfig.localModulesConfig); Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge}); module.exports=BatchedBridge;});

咱俩开掘这段JS代码中有那句 Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge});

总结

俗话说后生可畏图胜千言,整个运维进程用一张图总结起来正是:
图片 8

本文简要介绍了 iOS 端运转时 JS 和 Native 的互动进度,能够看看 BatchedBridge 在两个通讯进度中饰演了根本的角色。Native 调用 JS 是通过 WebSocket 或直接在 javascriptcore 引擎上试行;JS 调用 Native 则只把调用的模块、方法和参数先缓存起来,等到事件触发后透过返回值传到 Native 端,其他两个都保存了装有揭发的 Native 模块新闻表作为通讯的功底。由于对 iOS 端开荒并不熟练,文中如有错误的地方还请提出。

参谋资料:

  • GCD Reference
  • BRIDGING IN REACT NATIVE
  • React Native 应用钻探报告
  • React Native通讯机制详解

    1 赞 1 收藏 评论

图片 9

兵马不动未雨筹算知识,对JS很熟的同桌能够略过或指正

这段JS代码怎么知道吧,这几个是nodejs的模块代码,当打包成main.js之后,含义又有调换,大家差不离能够如此精通,__d() 是贰个概念module的JS函数,其就特别上面这段代码中的 define 函数,从代码上非常轻松能够知晓,它定义三个module,名字Id为BatchedBridge,同期传递了四个厂子函数,另三个模块的代码能够透过调用require获取这一个module,比方var BatchedBridge=require('BatchedBridge');

那是一个懒加运载飞机制,当有人调用require时,工厂函数才试行,在代码最终,把这一个模块要导出的剧情赋值给module.exports。

 function define(id,factory){ modules[id]={ factory:factory, module:{exports:{}}, isInitialized:false, hasError:false};} function require{ var mod=modules[id]; if(mod&&mod.isInitialized){ return mod.module.exports;}

好,我们赶紧回来,在上段代码中当BatchedBridge module创设时,通过那句 Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge}); 把自身定义到JSContext的全局变量上。所以在Native代码中能够经过 JSContext[@"__fbBatchedBridge"]获取到,

从代码中也足以观望BatchedBridge 是JS类MessageQueue的实例,並且它导出的时候并不曾导出构造函数MessageQueue,而是导出的实例BatchedBridge,所以它是React Native JS引擎中全局唯风流倜傥的。它也是Natvie和JS互通的关键桥梁。

 __fbBatchedBridge.callFunctionReturnFlushedQueue("AppRegistr","runApplication",["MGReactNative",{"rootTag":1,"initialProps":{}}])

我们三番玖重放MessageQueue 类的callFunctionReturnFlushedQueue 函数,它谈起底调用到__callFunction(module, method, args)函数

__callFunction(module, method, args) { var moduleMethods = this._callableModules[module]; if (!moduleMethods) { moduleMethods = require; } moduleMethods[method].apply(moduleMethods, args); }

看起来__callFunction正是最终的散发函数了,首先它从this._callableModules中找到模块对象,假诺它还从未加载,就动态加载它,借使找到就奉行最终的JS函数。

先看下AppRegistry是何等揭露给Natvie的

__d('AppRegistry',function(global, require, module, exports) { 'use strict'; var BatchedBridge=require('BatchedBridge'); var ReactNative=require('ReactNative'); var AppRegistry={ runApplication:function(appKey,appParameters){ runnables[appKey].run(appParameters); }, } BatchedBridge.registerCallableModule( 'AppRegistry', AppRegistry); module.exports=AppRegistry;});

有眼下的教学,未来看这些应该不态费力了,可以看到AppRegistry模块工厂函数中,推行了 BatchedBridge.registerCallableModule('AppRegistry',AppRegistry);,把团结注册到BatchedBridge的CallableModule中,所以在上焕发青新岁中,__callFunction才能在_callableModules找到AppRegistry实例,才具调用其runApplication函数。自身写的模块代码能够用React Native这种办法暴露给Natvie调用,和一向揭示的界别是,切合React Natvie的模块化原则,别的贰个直观的利益是你的模块能够是懒加载的,何况不会传染全局空间。

现阶段好不轻便把从N-JS的漫天路线跑通了,我们梳理下全部流程看看。

1 [RCTBatchedBridge enqueueJSCall:@“AppRegistry.runApplication” args:["MGReactNative",{"rootTag":1,"initialProps":{}}]];2 RCTJavaScriptContext callFunctionOnModule:@"AppRegistr" method:@"runApplication" arguments:["MGReactNative",{"rootTag":1,"initialProps":{}}] callback:(RCTJavaScriptCallback)onComplete//main.js3 __fbBatchedBridge.callFunctionReturnFlushedQueue("AppRegistr","runApplication",["MGReactNative",{"rootTag":1,"initialProps":{}}])//main.js4 BatchedBridge.__callFunction("AppRegistr","runApplication",["MGReactNative",{"rootTag":1,"initialProps":{}}])//main.js5 var moduleMethods = BatchedBridge._callableModules[module]; if (!moduleMethods) { moduleMethods = require; } moduleMethods[method].apply(moduleMethods, args);

接下去大家看看从JS如何调用Native,换句话说Native怎么着开放API给JS

我们以弹Alert框的接口为例,这是Native的OC代码,导出RCTAlertManager类的alertWithArgs:(NSDictionary *)argscallback:(RCTResponseSenderBlock)callback)方法

@interface RCTAlertManager() : NSObject <RCTBridgeModule, RCTInvalidating>...@end@implementation RCTAlertManagerRCT_EXPORT_MODULE()RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args callback:(RCTResponseSenderBlock)callback){...}#end
  • OC类实现RCTBridgeModule协议
  • 在.m的类达成中参预RCT_EXPORT_MODULE(),帮忙你达成RCTBridgeModule公约
  • 要导出的函数用RCT_EXPORT_METHOD()宏括起来,不用那么些宏,不会导出任何函数

这段时间从JS里能够如此调用这些艺术:

var RCTAlertManager=require('react-native').NativeModules.AlertManager;RCTAlertManager.alertWithArgs({message:'JS->Native Call',buttons:[{k1:'button1'},{k2:'button1'}]},function {console.log('RCTAlertManager.alertWithArgs() id:' + id +' v:' + v)});

执行之后的成效,弹出四个Alert

图片 10alert.png

对此详细的怎样导出函数推荐阅读Native Modules

我们明日的指标不是和美女喝茶聊天,是浓重美眉内心,是心中咳咳。来看看后天的机要

在JS中得以一贯动用RCTAlertManager.alertWithArgs来调用,表明JS中早已定义了和OC对象相呼应的JS对象,我们从导出二个Native类初阶,完整追踪下这一个进度。

RCTAlertManager类完毕了RCTBridgeModule和谐,而且在类的完结里带有了RCT_EXPORT_MODULE() 宏

@protocol RCTBridgeModule <NSObject> #define RCT_EXPORT_MODULE RCT_EXTERN void RCTRegisterModule; + (NSString *)moduleName { return @#js_name; } + load { RCTRegisterModule; }// Implemented by RCT_EXPORT_MODULE+ (NSString *)moduleName;@optional

在OC里,二个类所在文书被引用时,系统会调用其+load函数,当RCTAlertManager所在文件被引用时,系统调用load 函数,函数里差不离的调用RCTRegisterModule 把团结注册到一个大局数组RCTModuleClasses,那样系统中程导弹出的类都会自动注册到这几个全局变量数组里。

在JS中有二个BatchedBridge用来和Native通信,在Natvie中也会有一个RCTBatchedBridge类,它包裹了JSContext即JS引擎在RCTBatchedBridge start 函数中,做了5件事

  1. jsbundle文件的下载或本地读取
  2. 开始化导出给JS用的Native模块
  3. 初始化JS引擎
  4. 变迁配置表,并流入到JS引擎中,
  5. 执行jsbundle文件。
 //伪代码 - start{ //1 jsbundle文件的下载或本地读取 NSData *sourceCode; [self loadSource:^(NSError *error, NSData *source) {sourceCode = source}]; //2 初始化导出给JS用的Native模块 [self initModules]; //3 初始化JS引擎 [self setUpExecutor]; //4 生成Native模块配置表 把配置表注入到JS引擎中 NSSting* config = [self moduleConfig]; [self injectJSONConfiguration:config onComplete:^(NSError *error) {}); //5 最后执行jsbundle [self executeSourceCode:sourceCode];}

今昔大家最关怀第二步开端化Native模块 initModules 和moduleConfig 到底是怎么

//伪代码- initModules{ //遍历上节讲到的RCTGetModuleClasses全局数组,用导出模块的类或者实例创建RCTModuleData for (Class moduleClass in RCTGetModuleClasses { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); //这里一个很有意思的地方,如果导出的类或其任何父类重写了init方法,或者类中有setBridge方法 //则React Native假设开发者期望这个导出模块在Bridge第一次初始化时实例化,否则怎么样,大家想想 if ([moduleClass instanceMethodForSelector:@selector] != objectInitMethod || [moduleClass instancesRespondToSelector:setBridgeSelector]) { module = [moduleClass new]; } // 创建RCTModuleData RCTModuleData *moduleData; if  { moduleData = [[RCTModuleData alloc] initWithModuleInstance:module]; } else { moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; } //保存到数组中,数组index就是这个模块的索引 [_moduleDataByID addObject:moduleData]; }}

initModules里根据是还是不是重写init或增添了setBridge来决定是否要即刻实例化RCTGetModuleClasses里的导出类,然后用实例或类创立RCTModuleData,缓存到地头,以便JS调用时查询。

再来看第四步导出的 NSSting* config = [self moduleConfig] 是什么内容

 {"remoteModuleConfig": [["RCTStatusBarManager"], ["RCTSourceCode"], ["RCTAlertManager"], ["RCTExceptionsManager"], ["RCTDevMenu"], ["RCTKeyboardObserver"], ["RCTAsyncLocalStorage"], . . . ]} 

它唯有是一个类名数组。

传延宗族布置表后,通过上边包车型客车主意把那些类名数组注入到JSContext,赋值给JS全局变量__fbBatchedBridgeConfig

 [_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:onComplete];

在JS端,当有人引用了BatchedBridge var BatchedBridge=require('BatchedBridge');,其工厂函数会通过 __fbBatchedBridgeConfig配置表成立MessageQueue的实例BatchedBridge

 var MessageQueue=require('MessageQueue'); var BatchedBridge=new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, __fbBatchedBridgeConfig.localModulesConfig);

咱俩看看MessageQueue的构造函数,构造函数里为种种导出类创制了多个对应的module对象,因为那时config里独有一个导出类的名字,所以那边只为那个指标扩张了一个分子变量 module.moduleID,并把module保存到this.RemoteModules数组里

 _genModule(config, moduleID) { let module = {}; if (!constants && !methods && !asyncMethods) { module.moduleID = moduleID; } this.RemoteModules[moduleName] = module; }

接着大家蔓引株求看看这里使用的BatchedBridge.RemoteModules

NativeModules在伊始化时,用BatchedBridge.RemoteModules保存的类名列表,为每一种JS对象扩大了函数等品质

__d('NativeModules',function(global, require, module, exports) { 'use strict'; var RemoteModules=require('BatchedBridge').RemoteModules; var NativeModules={}; //遍历NativeModules中导出类名 Object.keys(RemoteModules).forEach(function(moduleName){ //把类名定义为NativeModules的一个属性,比如AlertManager类,定义只有就可以用NativeModules.AlertManager 访问 Object.defineProperty(NativeModules,moduleName,{ //这个属性(AlertManager)是可以遍历的,当然属性也是个对象里面有属性和函数 enumerable:true, //属性都有get和set函数,当调用访问这个属性时,会调用get函数 NativeModules.AlertManager get:function(){ var module=RemoteModules[moduleName]; if(module&&typeof module.moduleID==='number'&&global.nativeRequireModuleConfig){ //调用Native提供的全局函数nativeRequireModuleConfig查询AlertManager 导出的常量和函数 var json=global.nativeRequireModuleConfig(moduleName); module=config&&BatchedBridge.processModuleConfig(JSON.parse,module.moduleID); RemoteModules[moduleName]=module; } return module; } }); }); module.exports=NativeModules;});

React Native 把具备的Native导出类定义在一个NativeModules模块里,所以采纳Natvie接口时也足以直接那样得到对应的JS对象var RCTAlertManager=require('NativeModules').AlertManager;

代码里自身加了批注思量叁个主题素材,为何React Natvie搞的那么劳顿,为啥不在上三个步骤里(MessageQueue的构造函数)里就创设出如日中天体化的JS对象。

是的,就是模块的懒加载,即便Native导出了Alert接口,在JS引擎初叶化后,JS里只设有叁个名称叫AlertManager的空对象

图片 11906C7A90-0A85-4FD6-B433-39CE041D4445.png

当调用了RCTAlertManager.alertWithArgs({message:'JS->Native Call',buttons:[{k1:'button1'}时,才会调用AlertManager 的get函数到Native里询问导出的常量和函数,并定义到AlertManager中。

图片 127RGT1@Z}N19_9{KQ~P_SDFE.jpg

RCTAlertManager.alertWithArgs 那一个函数是什么样调用到Native里的吧,在BatchedBridge.processModuleConfig函数中,用_genMethod创造了一个闭包fn为各样函数赋值,那一个函数转调self.__nativeCall(module, method, args, onFail, onSucc); 大家调用RCTAlertManager.alertWithArgs函数,其实都以调用的那几个fn闭包。

 _genMethod(module, method, type) { fn = function { return self.__nativeCall(module, method, args, onFail, onSucc); }; return fn; }

__nativeCall,好熟稔的名字,

 __nativeCall(module, method, params, onFail, onSucc) { this._queue[MODULE_IDS].push; this._queue[METHOD_IDS].push; this._queue[PARAMS].push; global.nativeFlushQueueImmediate(this._queue); this._queue = [[],[],[]]; this._lastFlush = now; }

global.nativeFlushQueueImmediate 是Native提供的接口,__nativeCall把要求调用的module,method,params都塞到行列里,然后传递到Native,

大家在回来Native 找到上文提到的七个非常重要接口,Native模块查询接口:global.nativeRequireModuleConfig和调用接口global.nativeFlushQueueImmediate,他们是在JS引擎(JSContext)初叶化时,定义到全局变量的。

//RCTContextExecutor setUP//简化过的代码- setUp{ ... self->_context.context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { NSArray *config = [weakBridge configForModuleName:moduleName]; return RCTJSONStringify(config, NULL); }; self->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){ [weakBridge handleBuffer:calls batchEnded:NO]; }; ...}

[weakBridge handleBuffer:calls batchEnded:NO]; 经过后生可畏层层传递,调用到_handleRequestNumber 中,用moduleID找到RCTModuleData,再用methodID 找到id<RCTBridgeMethod> method 然后在moduleData.instance实例中实践

- _handleRequestNumber:(NSUInteger)i moduleID:(NSUInteger)moduleID methodID:(NSUInteger)methodID params:(NSArray *)params{ RCTModuleData *moduleData = _moduleDataByID[moduleID]; id<RCTBridgeMethod> method = moduleData.methods[methodID]; [method invokeWithBridge:self module:moduleData.instance arguments:params];}

那边有要求再重申一次moduleData.instance 这么些地点。

- (id<RCTBridgeModule>)instance{ if (!_instance) { _instance = [_moduleClass new]; ... } return _instance;}

还记的前面BatchedBridge 初阶化时的initModules吗

 //这里一个很有意思的地方,如果导出的类或其任何父类重写了init方法,或者类中有setBridge方法 //则React Native假设开发者期望这个导出模块在Bridge第一次初始化时实例化,否则怎么样,大家想想 if ([moduleClass instanceMethodForSelector:@selector] != objectInitMethod || [moduleClass instancesRespondToSelector:setBridgeSelector]) { module = [moduleClass new]; }

再不正是在客商真正调用时,在moduleData.instance里实例化,React Native已经懒到骨髓了。

RCTModuleData中种种函数的卷入 RCTModuleMethod里还会有二个优化点,JS传递到Native的参数须要开展响应的改换,RCTModuleMethod在调用函数只前,先预深入分析一下,成立每种参数转变的block,缓存起来,那样调用时,就直接利用对应函数指针实行参数转变了,大意详细询问能够看

  • processMethodSignature函数。

眼下大家为了直观,忽视了回调函数,alertWithArgs的第贰个参数是多少个JS回调函数,用来提醒客商点击了哪位button,并打字与印刷出风流倜傥行日志。

RCTAlertManager.alertWithArgs({message:'JS->Native Call',buttons:[{k1:'button1'},{k2:'button1'}]},function {console.log('RCTAlertManager.alertWithArgs() id:' + id +' v:' + v)});

回调函数的调用和平昔从Native调用JS是大半的,再回头看看__nativeCall 函数大家忽视的有的

 __nativeCall(module, method, params, onFail, onSucc) { //Native接口最多支持两个回调 if (onFail || onSucc) { onFail && params.push(this._callbackID); this._callbacks[this._callbackID++] = onFail; onSucc && params.push(this._callbackID); this._callbacks[this._callbackID++] = onSucc; } this._queue[MODULE_IDS].push; this._queue[METHOD_IDS].push; this._queue[PARAMS].push; global.nativeFlushQueueImmediate(this._queue); this._queue = [[],[],[]]; this._lastFlush = now; }

能够看见把onFail,onSucc三个函数类型转变为多少个数字ID插入到参数列表后边,并把函数函数缓存起来。从Native调用过来也比较轻松了,传递过callbackID到JS,就能够实行到回调函数。

JS传递的参数仅仅是个整形ID,Native怎么着晓得这一个ID正是个回调函数呢?

答案是和此外参数同样通过Native的函数签字,若是开采对应的参数是个block,则从JS传递过来的ID正是相应的回调ID,把其转变为RCTResponseSenderBlock的闭包。

RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args callback:(RCTResponseSenderBlock)callback)

到此结束,我们曾经把意气风发切JS->Natvie的流程都走通了,梳理一下总体流程。

图片 13调用图.png

总括一下

  1. Native初叶化时, Native生成要导出模块的名字列表,仅仅是模块名字列表, ModuleConfig
  1. 在React Native 的JS引擎开端化完毕后,向JSContext注入ModuleConfig,赋值到JS全局变量 __fbBatchedBridgeConfig
  2. 还记得特别N->JS大使---JS对象BatchedBridge吗,BatchedBridge创立的时候会用__fbBatchedBridgeConfig变量里Native模块名字列表定义一个同名的JS对象,然则是一个不曾别的方法的空对象,只增添了几个获取方式数组的get函数。此时起首化的操作已形成。
  3. 比较久相当久以后,有人用RCTAlertManager.alertWithArgs 调用了Native的代码,咳咳,这人是自己,此时JS去获取RCTAlertManager方法列表时,挖掘是空的,就调用Native提供的查询函数nativeRequireModuleConfig 获取RCTAlertManager对象的详实的导出新闻,并定义成同名的JS函数,此函数转调到OC的兑现
  4. 此时RCTAlertManager对应的JS对象才定义完整,JS找到了alertWithArgs函数,种种对应的JS函数都是多少个包裹了调用__nativeCall的闭包,JS通过此函数转载到Native

能够见见,Native导出的配置表并非在一同来就完全的注入JS并定义对应的JS对象,而是风姿浪漫味注入了一个模块名字,当运转时期有人调用的时候,才再从Native查询调用模块的亲力亲为安顿表,这种懒加运载飞机制化解了一个巨型的app导出的Api比非常多,全部导入JS导致开头化时内部存款和储蓄器占用过大的标题。

线程难点

React Native为JS引擎创造了二个独门的线程

//RCTJavaScriptContext- (instancetype)init{ NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoopThread) object:nil]; javaScriptThread.name = @"com.facebook.React.JavaScript"; [javaScriptThread start]; return [self initWithJavaScriptThread:javaScriptThread context:nil];}

具有的JS代码都运作在"com.facebook.React.JavaScript"后台线程中,全体的操作都以异步,不会卡死主线程UI。况且JS调用到Native中的接口中有强制的线程检查,借使不是在React线程中则抛出十三分。这样有叁个难点,从JS调用Native中的代码是实践在这里个后台线程中,我们上文的RCTAlertManager.alertWithArgs鲜明是个操作UI的接口,实行在后台线程会crash,在导出RCTAlertManager时,通过达成格局- (dispatch_queue_t)methodQueue,原生模块能够钦点本身想在哪个队列中被实践

- (dispatch_queue_t)methodQueue{ return dispatch_get_main_queue();}

恍如的,假如一个操作要求费用相当短日子,原生模块不该阻塞住,而是应该声Bellamy个用于实施操作的单身队列。比如,RCTAsyncLocalStorage模块制造了一德一心的二个queue,那样它在做一些很慢的磁盘操作的时候就不会卡住住React自己的消息队列:

- (dispatch_queue_t)methodQueue{ return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);}

React的消息循环

那是非凡的事件驱动机制和音信循环,当无别的事件时,runloop处于睡眠景况,当有事件时,举例客户操作,电磁照应计时器到时,网络事件等等,触发风流倜傥此音信循环,最总表现为UI的更换或数额的成形。

图片 14音讯循环.png

此地要留心的是,以上我们讲到从 JS调用到Native是调用global.nativeFlushQueueImmediate 立刻实行的。React音讯循环这里做了二次缓存,举个例子客户点击壹回,全部触发的JS->N的调用都缓存到MessageQueue里,当N->JS调用实现时,以再次来到值的花样再次回到MessageQueue, 收缩了N->JS的交互次数。缓存时间是 MIN_TIME_BETWEEN_FLUSHES_MS = 5微秒内的调用。

 __nativeCall(module, method, params, onFail, onSucc) { this._queue[MODULE_IDS].push; this._queue[METHOD_IDS].push; this._queue[PARAMS].push; var now = new Date().getTime(); if (global.nativeFlushQueueImmediate && now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) { global.nativeFlushQueueImmediate(this._queue); this._queue = [[],[],[]]; this._lastFlush = now; } }

MIN_TIME_BETWEEN_FLUSHES_MS岁月内的调用都会缓存到this._queue,以重返值的方式重返给Native,造成贰次消息循环

 callFunctionReturnFlushedQueue(module, method, args) { guard => { this.__callFunction(module, method, args); this.__callImmediates; return this.flushedQueue(); } flushedQueue() { this.__callImmediates(); let queue = this._queue; this._queue = [[],[],[]]; return queue[0].length ? queue : null; }

本篇的开始和结果正是那一个,想看懂轻松,想尽量简洁明了的总括成文字真是生机勃勃件十分不便于的事情,特别是此处相当多JS的代码。有题目我们留言指正。下生机勃勃篇将介绍ReactNative的渲染原理。

本文由设计建站发布,转载请注明来源:构建跨平台的原生应用,通讯及消息循环代码剖