React Native (以下称 R-N )是 Facebook 开源的跨平台开发框架,让开发者能够用 Javascript (以下称JS) 就可以开发 Native App 应用。能够像写类似 React.js 的代码写法去写 R-N。但是没有那些 <div> 之类的 html 标签,而是使用像 Text 和 View 类似 Objective-C(以下称OC)的原生组件来写UI,用 StyleSheet 对象来写类似CSS的样式。
JS 变成了跨平台的语言。可以想象将来只要掌握 JS,就可以替代原本的 iOS 和 Android 开发工作,甚至是桌面开发。让一个程序员成为“全栈工程”。对于公司老板来讲,意味着可以雇佣更少的程序员了。:-(
想要学习 React Native 入门开发,可以从知乎的这篇文章入手。
开始之前,我们先看俩个概念:
Objective-C Runtime
Objective-C 是基于C加入了面向对象特性和消息转发机制的动态语言,除编译器之外,还需用Runtime系统来动态创建类和对象,进行消息发送和转发。把代码执行的决策从编译和链接的时候,推迟到运行时。这给程序员写代码带来很大的灵活性,比如说你可以把消息转发给你想要的对象,或者随意交换一个方法的实现之类的。
JavascriptCore & JS 如何调用 OC
iOS 7 引入了 javascriptCore 库,使得 OC 和 JS 可以互相调用。有俩种方式可以让 JS 调用 OC:
-
Blocks :在 OC 下把 block 代码赋予一个 JSContenxt 标识符,javascriptCore 就会自动把 block 封装在javascript函数里。
-
JSExport 协议 :在 @protocol 中定义的函数,都能够在 JS 中访问到(独立的函数,非在类中定义的函数)。
R-N 主要是以Block 的方式,也有一部分是以 JSExport 的方式。
我们先来看 R-N 的 ios 事例代码,如下
var ios = React.createClass({
render: function() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
);
}
});
(注:以下函数方法链接会跳转到R-N 在 github 上源码的对应行)
R-N 的 通过 render 方法渲染了页面所有的 UI 组件,这里有 View 、Text等组件,还包括组件引用的 StyleSheet 对象。R-N 的组件都是通过 React.createClass 创建,在 ReactClass.js 找到 createClass 方法,ReactClass 实现了接口 ReactClassInterface ,像 getDefaultProps、componentDidMount 都是在接口里被定义的。JS 是如何在Class 中实现接口? 代码如下
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
Constructor 是 ReactClassComponent ,由 ReactComponent.prototype 和 ReactClassMixin 俩部分组成。在ReactComponent 中可以看到 setState 方法去异步的更新组件(通过message queue去更新state)。所以 setState 之后,如果立即去访问 this.state 可能还是返回之前的值。
JS 调用 OC 的方法的流程大概是如下:(如有不准确的地方,望指正)
1. 在 ReactNativeBaseComponent 中的 RCTUIManager.createView 即是一个 OC 函数的调用,看名字大致也能看出来是用来创建组件的。
2. 而 RCTUIManager 的来源是 NativeModules,NativeModules 的来源是 BatchedBridge,BatchedBridge 来自MessageQueue
3. MessageQueue 的参数 __fbBatchedBridgeConfig 配置是在 OC 代码中写入的,在 OC 代码初始化时候即被执行,告诉 JS 有哪些方法是可以被 JS 调用的。
4. JS 调用 OC 方法是在 MessageQueue 的 __callFunction 中。BridgeProfiling.profile 是判断参数是否是一个function,然后执行。
那 OC 是如何暴露自己的方法给 JS 呢?
答案是 RCT_EXPORT_METHOD 标记,通过它可以把一个 OC 函数暴露给JS。没错,之前的RCTUIManager.createView 就是调用的这里的 OC 函数。还有另外一个标记 RCT_EXPORT_MODULE,通过它可以把整个模块暴露给 JS。
RCT_EXPORT_MODULE 调用 RCTRegisterModule 把模块注册到 RCTModuleClasses,在 OC 初始化模块时通过 RCTGetModuleClasses() 就可以获取到所有暴露出来的模块,并且以 JSON 格式写入 JS 的配置。
RCT_EXPORT_METHOD 通过给函数加上 rct_export 前缀,在运行时通过 class_copyMethodList 获取到一个模块的所有函数,并把前缀是 rct_export 筛选出来。同时在OC 初始化的 initModules 之后,执行 RCTProfileHookModules(self) ,在 RCTProfileHookModules 中用 Runtime 的方式 把这些暴露出来的函数,放到了 Block 中,这样 JS 就可以直接调用了。
总结
在 React-Native 中,JS 调用 OC 流程大致如下:
JS call => JS Bridge => OC Bridge => OC modules & functions
OC 代码通过 EXPORT_* 方法去定义暴露模块和函数,并通过 Runtime 的方法去从新包装 OC 代码到 Block 中,这样 JS 就可以调用 OC 代码了。