干货 | 携程酒店iOS动态View的探索
作者简介
姜睿东,2009年加入携程,从事无线研发,现在大住宿事业群负责酒店无线研发工作。
一直以来,Native App因为审核的原因,新版本不能很及时地上线。尤其是iOS,碰到点审核问题,有时候一连几天都不能上架,严重影响业务和产品的体验。
大家一直都在寻求能够动态更新业务的方法,关于这方面的框架也是层出不穷。自从Facebook推出React Native以后,便以其良好的兼容性和性能优势占据了这方面的领先地位,携程也在此基础上开源了CRN框架。
如果是新业务,用CRN开发是非常合适的,开发效率高,双平台兼容性好。但如果要把已有的Native页面转CRN,复杂的核心页面成本会有点高。在不增加人手的情况下,要想同时进行业务的迭代和CRN的转换,会有点力不从心。
如果硬转,周期会很长。以携程酒店主流程页面之一的订单详情页为例,在没有额外增加人手的情况下,前后花了几个月时间,才陆陆续续完成了90%的功能转CRN,过程尤为艰辛。订单详情页是主流程页面中相对简单的,如果要转酒店详情页,光是几百行的ViewModel就已经让人望而却步了。
对此,我们考虑能不能采用一种让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要强大灵活许多,想象空间也大了很多。所以我们接下去就准备在这基础之上做点文章。
第一步,先创建一个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控件来做示例:
render() { 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,可以有无限多的可能。
【推荐阅读】
近万字长文详述携程大规模应用RN的工程化实践
Electron在DevTools中的探索与实践
浅谈Node.js在携程的应用
云计算时代携程的网络架构变迁
携程酒店小程序开发背后的“黑科技”
干货 | 携程酒店iOS动态View的探索相关推荐
- 干货 | 携程酒店安卓地图开发实践
作者简介 亦枫,携程资深软件工程师,负责酒店业务 Android 客户端的相关研发工作. 当前大多数移动互联网 App 都会存在地图相关功能,尤其是 LBS(基于位置服务)相关的业务,依赖性更强,携程 ...
- 干货 | 携程酒店Flutter性能优化实践
作者简介 Qifan,携程高级工程师,专注移动端开发:Yinuo,携程高级工程师,专注移动端开发:popeye,携程软件技术专家,关注移动端跨端技术,致力于快速,高性能地支撑业务开发. 一 .前言 携 ...
- 干货 | 携程酒店MOCK全链路实践
作者简介 刘晓攀,携程酒店性能测试负责人,专注性能测试分析和辅助测试工具的开发. 一.前言 Mock在整个软件开发测试周期中已经非常普遍,我们也会经常有意无意地使用它.譬如开发了一段代码,这段代码强依 ...
- 干货 | 携程酒店RSocket实践
作者简介 刘诚,携程酒店研发性能架构师.2014年加入携程,致力于通过架构的演进,控制企业硬件成本. 一.初识RSocket 在QCon2019北京大会上第一次得知RSocket.印象深刻的是Neti ...
- 干货 | 携程酒店小程序开发背后的“黑科技”
作者简介 崔广宇,携程酒店研发部小程序开发经理,曾负责过反爬虫开发以及H5开发. 本文将分享携程酒店小程序的一些开发经验, 和一些非技术的经验.这里的小程序包括微信小程序,支付宝,百度,头条.快应用因 ...
- 干货 | 携程酒店实时数仓架构和案例
作者简介 秋石,携程数据仓库专家,关注大数据.数据仓库.数据治理等领域: 九号,携程数据技术专家,关注数据仓库架构.数据湖.数据治理: 魁伟,携程资深数据工程师,关注实时&离线大数据产品及技术 ...
- 干货 | 携程酒店搜索引擎AWS上云实践
作者简介 宮娴,携程高级后端开发工程师:Spike,携程高级后端开发专家. 随着携程国际化业务的快速推进,搜索引擎作为用户体验中至关重要的一环,上云变得志在必行.本文主要分享酒店搜索引擎迁移AWS的探 ...
- 干货 | 携程机票Sketch插件开发实践
作者简介 尹正波,携程机票研发部前端工程师,专注设计和开发的交叉领域,用系统和工具改进设计体验和交付. Sketch 是伴随移动应用程序崛起而流行的 UI 设计工具.2014年 Sketch V3 增 ...
- 干货 | 从47%到80%,携程酒店APP流畅度提升实践
作者简介 Jin,携程高级研发经理,专注移动技术开发:Dan,携程测试开发经理,关注数据挖掘以及数据在系统质量提升中的应用:Lanbo,携程软件技术专家,专注移动技术开发. 一.背景 APP性能提升一 ...
最新文章
- 为什么一般要定义析构函数为虚析构函数
- 从员工出走仅剩 5 人,到一支打胜仗的铁军
- dos中特殊符号命令的应用
- Give root password for maintenance 问题解决.
- java将json转为hashmap_将JSON字符串转换为HashMap
- 高文院士:从“乡村教师”到人工智能掌舵者的40年科研路
- 让Ubuntu更多的使用物理内存
- vscode+vim使用技巧
- android file transfer下载_PHP通过header方式下载文件
- oracle打印awr报告,oracle生成awr报告
- for循环遍历Set集合时如何判断是否有下一个元素
- 对一些稀奇古怪面试题的理解
- wps文字如何取消英文首字母输入时自动变大写
- 刷入twrp_twrp刷入面具进入recovery(twrp)的方式获取root刷入第三方rom获取第三方rom包类原生rom包的网络连接受限问题
- PhotonServer教程《五》
- ExecutorExecutorService
- css向上三角,css实现三角
- Task5样式色彩秀芳华
- python入门笔记(1)
- 技术科普丨解密无处不在的EMC干扰
热门文章
- C++ 压缩文件夹(一)
- android 自带网络共享,安卓手机如何共享自身网络
- 【天下】探访欧洲最人性化监狱
- 学计算机买哪种手机好,想购买手机〜您能推荐哪款手机吗?因为是学生
- 小米VR一体机游戏开发日记(第二天)
- 服务器中电池可以维修吗,电池修复到底有没有效果?一个维修工的遭遇揭开真相!...
- JavaScript 函数式编程——入门指南
- ChatGPT开源平替(1)——ChatGLM
- IOS 申请证书 出现用户名密码错误的解决
- android 投屏 app 三星,[图]为何Windows 10的Android应用投屏功能仅限于三星手机?