大家始终都在寻求可能动静更新业务的办法,对于这方面的框架也是层出不穷。自从 Facebook 推出 React Native 当前,便以其良好的兼容性和性能劣势占据了这方面的领先地位,携程也在此基础上开源了CRN 框架。

如果是新业务,用CRN 开发是十分适合的,开发效率高,双平台兼容性好。但如果要把已有的Native 页面转CRN,简单的外围页面老本会有点高。在不减少人手的状况下,要想同时进行业务的迭代和CRN 的转换,会有点力不从心。

如果硬转,周期会很长。以携程酒店主流程页面之一的订单详情页为例,在没有额定减少人手的状况下,前后花了几个月工夫,才陆陆续续实现了90% 的性能转CRN,过程尤为艰苦。订单详情页是主流程页面中绝对简略的,如果要转酒店详情页,光是几百行的ViewModel 就曾经让人望而生畏了。563513413,不论你是大牛还是小白都欢送入驻

对此,咱们思考能不能采纳一种让Native 和CRN 共存的形式,这样既能够保留Native 的业务逻辑,又能够在UI 层面做到灵活应变。最要害的是,能够分模块的开发,而不必像转CRN 那样必须整个页面一起上。

当然,Native 和CRN 混合的解决方案早就有了,然而当CRN 作为一个子View 呈现在Native 页面里的时候,因为CRN 的框架比拟重量级,在性能上并不是特地现实,而且和Native 的交互也不是特地不便,所以咱们开始思考有没有更为轻便的解决方案

在比拟了多种跨平台计划之后,首先排除了相似Lua 这种须要依赖第三方库,且语法非主流的计划,最终决定采纳原生零碎就自带反对的,且语法有着宽泛大众根底的JavaScript。

在iOS7 之前,要在Native 环境中和JavaScript 交互是非常简单且性能无限的,基本上只有依附Webview 的EvaluateJavaScript 来注入执行一段JS 脚本。从iOS7 开始,苹果引入了JavaScriptCore 这个库,登时给iOS 的开发带来了天翻地覆的变动。

为什么会这么说呢,首先来看一下JavaScriptCore 中所蕴含的两个要害类,JSContext 和JSValue:

JSContext

JSContext 提供了一个在 APP 中执行 JavaScript 代码的环境,使得咱们能够间接在 Objective-C 或 Swift 代码中间接调用 JavaScript 代码,并失去返回后果,反过来也能够裸露办法和类供 JavaScript 调用。

JSValue

JSValue 则是一个 JavaScript 数据类型在 Objective-C 或 Swift 中的包装对象,借助于这个对象咱们能够在 Native 代码和 JavaScript 代码之间相互传值,这两者之间的对应关系如下图所示:

Objective-C (and Swift) Types

JavaScript Types

nil

undefined

NSNull

null

NSString (Swift String )

String

NSNumber and primitive numeric types

Number, Boolean

NSDictionary (Swift Dictionary)

Object

NSArray (Swift Array )

Array

NSDate

Date

Objective-C or Swift object ( id or AnyObject) Objective-C or Swift class ( Class or AnyClass)

Object

Structure types: NSRange , CGRect , CGPoint , CGSize

Object

Objective-C block (Swift closure)

Function

简略总结一下,JSContext 提供 JavaScript 和 Native 相互调用的接口,JSValue 提供相互调用之间的数据类型转换,这样的调用办法比之前的 Webview 要弱小灵便许多,设想空间也大了很多。所以咱们接下去就筹备在这根底之上做点文章。563513413,不论你是大牛还是小白都欢送入驻

第一步,先创立一个 JavaScript 对象,用来形容对应 iOS 中的 UIView,代码用 ES6 如下:

复制代码

Class View {    constructor() {         this.x = 0;         this.y = 0;         this.width = 0;         this.height = 0;         this.borderWidth = 0;         this.borderColor = ‘’;         this.cornerRadius = 0;         this.masksToBounds = false;         this.subviews = [];     }     initWithFrame(x, y, width, height) {        ……     }     addSubview(v) {        ……        }     setOnclick(click) {        ……        }     ……}复制代码

这些属性和办法都是 iOS 中 UIView 比拟罕用的,如同在 iOS 中 UILabel 是继承自 UIView 一样,咱们持续创立一个 JavaScript 的 Label 对象,并继承自方才在下面创立的 View 对象。

复制代码

Class Label extends View {    constructor() {         super();         this.text = ‘’;         this.textColor = ‘’;         this.textSize = 14;         this.fontStyle = 0;         this.textAlignment = 0;         this.lineBreakMode = 4;         this.numberOfLines = 1;    }    }复制代码

以此类推,咱们持续创立诸如 Imageview,Button,ScrollView 等 iOS 中罕用的组件,只有违心,所有的组件都能够用这种形式来描述。

有了这些根底的 JavaScript 组件,接下去就能够如同在 iOS 中布局一样,开始用这些组件进行布局,如下代码片段示例了如何对一张图片进行布局。

复制代码

createImage() {       var container = View.initWithFrame(0, 0, 50, 50);       container.backgroundColor = "#FFFFFF";       var image = Image.initWithFrame(0, 0, 50, 50);       image. imageUrl = ‘http://m.ctrip.com/xxxxx.png’;       container.addSubView(image);       return container; }复制代码

对于相熟 iOS 开发的同学来说,会感觉这段代码十分眼生。没错,这就是一段用 JavaScript 来写的 iOS 代码,依此类推,略微简单一点的布局也能够用这种形式实现。

最初来看一下布局实现当前的返回值,临时还是先以下面的 Image 控件来做示例:

复制代码

 varcontainer = View.initWithFrame(0, 0, 50, 50);       container.backgroundColor = "#FFFFFF";       var image = Image.initWithFrame(0, 0, 50, 50);       image. imageUrl = ‘http://m.ctrip.com/xxxxx.png’;       container.addSubView(image);        var demoView = View.initWithFrame(0,0,180,180);       demoView.addSubView(container)       return demoView;}复制代码

如果在浏览器或者 JavaScript 环境中运行上述代码,会失去一个自定义的递归对象,根对象会蕴含一个 Subview 数组,数组中的每个元素都有可能是另外一组 UI 对象,当然实际操作中并不倡议档次太多,个别 1-2 层。

做到这里,JavaScript 的局部暂告一段落。接下来回到 Native 当中,还记得上文提到的 JSContext 么?这是一个在 Native 当中的 JavaScript 执行环境,咱们在 Native 环境中用 JSContext 来执行方才那个 Demo,就会失去一个对应的 JSValue 值,这个 JSValue 的值用 [JSValuetoObjct] 来转换成 Object-C 对象的话,最终就失去了一个字典,NSDictionary。

持续递归地拆解这个字典,拆解到底,每个元素最终都会转成 OC 的 Object,而后依据每个 Object 事后定义好的 Type 类型,实例化成相应的 Native 组件,并且每个组件有一个对应的数据 Model。

还是以上述那个 Label 为例,其对应的 OC Label 代码如下:

复制代码

@implementation Label - (void)setModel:(HTLDynamicLabelModel *)model{   self.dynamicViewModel = model;   self.text = model.text;   self.textColor = model.textColor;   self.font = model.font;   self.lineBreakMode = model.lineBreakMode;   self.numberOfLines = model.numberOfLines;       if(model.richText && model.richText.attributedString) {       self.attributedText = model.richText.attributedString;    }} @end复制代码

到此为止,就实现了所有之前在 JavaScript 中描述的控件在 Native 里的转换,剩下的事件就是对这些 Native 组件进行渲染了,具体就不在这里形容了。

总体来说,这个思路在原理上跟 RN 或者 CRN 是一样的,但更为轻量一点,简直 0 配置就能应用。通过配置增量更新,从服务端下载最新的 JS 文件,能够做到相似 CRN 在线更新的成果。

从性能上来看,因为不须要额定加载任何框架代码,JS 执行的耗费简直能够疏忽,所以和 Native 混合在一起的时候,简直看不出有任何提早。

这个计划非常适合做一些轻量级的又须要常常不定期更新的 UI,比方节日气氛或者城市包装的 UI。这些 UI 常常会追随节假日更新,用这个计划能够轻松在线更新 UI,不必通过服务端下发一堆款式来管制,加重了服务公布的压力和不必要的服务交互。

综上所述,这是咱们团队对新事物的一些探讨和钻研,并不存在要代替 CRN 或其余框架一说,每个框架都有其实用的场景,没有相对的优劣之分。

在钻研这个解决方案的过程中,咱们也认真地深刻理解了 JavaScriptCore 的一些机制,原理都是万变不离其宗的,但能够联合不同的场景,进行不同的演变,就看怎么灵活运用了。

所以,与其说本文是在摸索 iOS 中动静 View 的解决方案,也无妨说成是对 JSContex 和 JSValue 如何使用的一些探讨,从理论的摸索中来看,灵活运用好 JavaScriptCore,能够有有限多的可能。