共计 4412 个字符,预计需要花费 12 分钟才能阅读完成。
大家始终都在寻求可能动静更新业务的办法,对于这方面的框架也是层出不穷。自从 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,能够有有限多的可能。