专栏首页iOS面试技术问题iOS动态View的探索
原创

iOS动态View的探索

大家一直都在寻求能够动态更新业务的方法,关于这方面的框架也是层出不穷。自从 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](https://developer.apple.com/documentation/foundation/nsnull?language=objc)

null

[NSString](https://developer.apple.com/documentation/foundation/nsstring?language=objc) (Swift [String](https://developer.apple.com/documentation/swift/string?language=objc) )

String

[NSNumber](https://developer.apple.com/documentation/foundation/nsnumber?language=objc) and primitive numeric types

Number, Boolean

[NSDictionary](https://developer.apple.com/documentation/foundation/nsdictionary?language=objc) (Swift Dictionary)

Object

[NSArray](https://developer.apple.com/documentation/foundation/nsarray?language=objc) (Swift [Array](https://developer.apple.com/documentation/swift/array?language=objc) )

Array

[NSDate](https://developer.apple.com/documentation/foundation/nsdate?language=objc)

Date

Objective-C or Swift object ( [id](https://developer.apple.com/documentation/objectivec/id?language=objc) or AnyObject) Objective-C or Swift class ( [Class](https://developer.apple.com/documentation/objectivec/class?language=objc) or AnyClass)

Object

Structure types: [NSRange](https://developer.apple.com/documentation/foundation/nsrange?language=objc) , [CGRect](https://developer.apple.com/documentation/coregraphics/cgrect?language=objc) , [CGPoint](https://developer.apple.com/documentation/coregraphics/cgpoint?language=objc) , [CGSize](https://developer.apple.com/documentation/coregraphics/cgsize?language=objc)

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 控件来做示例:

复制代码

        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,可以有无限多的可能。

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • 干货 | 携程酒店iOS动态View的探索

    一直以来,Native App因为审核的原因,新版本不能很及时地上线。尤其是iOS,碰到点审核问题,有时候一连几天都不能上架,严重影响业务和产品的体验。

    携程技术
  • iOS各View的属性设置

    剑行者
  • iOS探索 -- KVO 的原理分析

    KVO (key-value-observing) 是一种 键值观察 机制, 它允许当前对象去观察目标对象的某个属性的变化; 当被观察对象的属性发生变化后, 会...

    程序猿川子
  • Android动态添加view的方法示例

    这种写法数量一般3-6个还是可以的,如果太多的话还是推荐用RecyclerView。

    砸漏
  • iOS底层探索——分类的加载分析

    在上篇文章类的加载分析中,分析了非懒加载类的加载流程,ro、rw、rwe的逻辑,方法的排序流程等,本篇将重点分析懒加载类和分类的加载过程。

    CC老师
  • iOS-UISearchController 的 Bug 之 Attempting to load the view of a view controller while it is dealloca

    用户1890628
  • ios-ScrollView添加到view上,view的touchesBegan无法执行

    这几天做的时候碰到了这么个问题,就是当我们把ScrollView添加到控制器的view上,或者添加到UICollectionViewCell上面,scrollV...

    全栈程序员站长
  • iOS 点击事件如何穿透透明的View?

    赵哥窟
  • ios的动态库和静态库

    当你创建一个framework文件时,系统“默认”是一个动态库的格式,如果想做成静态库,需要在buildSetting中将Mach-O Type选项设置为Sta...

    剑行者
  • iOS模拟动态定位的测试方案

    上一篇文章《iOS移动应用模拟定位的非侵入式测试方案》将了如何模拟静态的定位信息,今天来补充一下如何模拟动态的定位信息,也就是模拟手机移动的状态,比如在打车AP...

    岛哥的质量效能笔记
  • 探索电子游戏的动态难度调整(CS)

    电子游戏是当今世界上最大的娱乐产业之一。成为这个行业的一部分意味着要与许多其他公司和开发者竞争,因此,建立粉丝基础至关重要。有趣的游戏可以获得公司客户即粉丝的支...

    N乳酸菌
  • iOS_scrollView title and line view, 滚动的标题和线

    mikimo
  • 在iOS中怎样创建可展开的Table View?(下)

    我猜这部分可能是你最期望的了,因为本次教程的目标将会在在部分实现.第一次我们设法让顶层的cell,在它们点击的时候展开或者合拢.以及显示或者隐藏合适的子cell...

    hrscy
  • iOS里的动态库和静态库

    静态库:链接时,静态库会被完整地复制到可执行文件中,被多次使用就有多份冗余拷贝(图1所示)

    且行且珍惜_iOS
  • 在iOS中怎样创建可展开的Table View?(上)

    几乎所有的app都有一个共同特征,它们向用户提供了多个视图控制器来导航和工作.这些视图控制器可以用在很多方面,例如,简单地显示某种信息在屏幕上,或者从用户的输入...

    hrscy
  • iOS自动化探索(二)WDA API的使用

    前面我们已经安装好了WebdriverAgent, 现在可以用Facebook官方提供的API来进行一些操作

    周希
  • 探索IOS App的生命周期之启动篇

    因为刚刚涉入IOS APP开发,对IOS应用的使用的生命周期不是很熟,所以写下此日志,以防时间一长又忘记了。

    江中散人_Jun
  • Android 动态添加view或item并获取数据的实例

    最近在做一项目,项目中用到了一个功能,要求是动态Item,而且是多个的情况下,不过仔细的分析了下,都大同小异,做起来也很简单,在这里我只抽取出来做了一demo,...

    砸漏

扫码关注腾讯云开发者

领取腾讯云代金券