客户端布局是客户端开发最为基础也是最为重要的编程实践之一,对于一个有着良好技术架构的客户端开发团队来说,布局编码工作占业务开发工作量的80%。显然,对于布局编码进行深入的思考、总结统一最佳实践、提供有力的基础架构支撑,对于提升客户端开发团队的生产力非常关键。

同时,客户端动态化是当前移动开发领域的技术热点,也是移动开发未来最为重要的发展方向之一。当前业界涌现出了React Native和Weex等解决方案,但是这里就存在迷一样的事实:所有框架的指导思想都是保持Web基本布局实践的前提下,通过原生渲染来提高性能。但是,在移动互联网时代,开发者应该扩展一下问题解决思路:Web及其Web的布局实践不是唯一的道路,甚至不可能是最好的思路。

本文从点评App iOS团队的布局实践——匠心布局开始说起,向大家展示一种反常规甚至反直觉的布局方案,和点评移动团队按照匠心布局全新设计的跨平台动态化布局框架毕加索(picasso)。

匠心布局

匠心布局原本是点评iOS布局逻辑开发最佳实践的总结,随着跨平台动态化布局框架毕加索(picasso)的推出,使匠心布局成为跨平台的标准实践。

布局逻辑的本质

在对比布局方案和思路之前,我们先思考一下布局需求的本质是什么?

布局的本质就是: 1. 指定视图的大小和位置 2. 响应变化,在产生变化的时候重新指定大小和位置

变化的来源

我们可以将变化的来源划分为两个方面,内部来源和外部来源: 1. 内部来源 内容变化 国际化 动态字体

2. 外部来源
    多屏幕适配
    来电录音提示条
    设备旋转

说起适配,可能大家首先想到的就是针对屏幕的适配,实际上内部变化来源中的内容变化一直都是布局编码的核心需求,布局总会涉及到诸如:根据文本长度调整布局,根据具体业务特点排布icon等需求。

外部来源总结下来就是对视图容器变化的适配,这里其实并不能简单的等同于对机型分辨率的适配、设备旋转、来电录音提示条、分屏设计等因素的组合效应。

简单回顾下iOS布局需求发展历史(图片来自WWDC):

曾经有那么一段时间,iOS开发者的世界还是简单而美好的:)

然后我们迎来了iPhone 5、iPhone 6、iPhone 6 Plus……这些设备的出现在iOS技术史上具有里程碑的意义,iOS开发者需要适配更多的屏幕,当然苹果也推出了Auto Layout作为应对方案。当然,既然能适配iPhone6 和 iPhone6 Plus, 简单的、粗暴的适配iPad也成了顺理成章的事情。

当然,所有的iOS设备都有横屏的能力……

再加上iOS大屏幕上分屏的能力,iOS开发面临的适配挑战已经比肩甚至超过Android开发小伙伴了,因为老板和UED通常要求iOS设备的完美适配。

点评iOS 最佳布局实践

点评iOS的布局实践总结起来非常简单的,其中核心的特点是: 1. 纯代码布局 2. no autolayout 其中纯代码布局指的是全完使用Objective-C完成布局逻辑,不使用Interface Builder,也不使用Interface Builder(IB)衍生出来的autolayout,以及任何基于autolayout的DSL实现。

事实上这并不是一个全新的思路,我们在苹果方案的文档中找到这样一段话: 1. programmatically defining a view’s frame provides the most flexibility and power. 2. When a change occurs, you can literally make any change you want.

显然苹果方案也认为纯代码布局有着灵活性巨大优势,但是他们并没有想清楚具体的布局编码实践,然而点评的匠心布局凭借“锚点”的概念完美的解决了布局代码难以编写,难以维护的问题。在这样的前提之下,Auto Layout之类声明式方案没有任何优势,却因为核心引擎实现的复杂度带来稳定性和性能方案的重重问题。

锚点的力量

逻辑表达的困境

使用具备完整编程语言表达能力的布局,显然在灵活性和性能方面具备压倒性的优势。但是,无论Android还是iOS平台上,原生API写出的布局代码都显得非常拙劣,难以编写和理解,进而变得难以维护。 例如就控件居中的需求来讲,iOS的原始方案可能是这样:

CGRect frame = view.frame;
frame.origin.x = bgView.frame.size.width / 2 - view.frame.size.width / 2;
frame.origin.y = bgView.frame.size.height / 2 - view.frame.size.height / 2;
view.frame = frame;

可能是这样的:

view.frame = CGRectMake(bgView.frame.size.width / 2 - view.frame.size.width / 2,
                        bgView.frame.size.height / 2 - view.frame.size.height / 2,
                        view.frame.size.width,
                        view.frame.size.height);

也可能是这样的:

view.center = CGPointMake(bgView.frame.size.width / 2 - view.frame.size.width / 2,
                          bgView.frame.size.height / 2 - view.frame.size.height / 2);

然而这样的布局逻辑代码显然是不可维护的,由此产生的“纯代码布局是脑残行为”的论断也是可以理解的。苹果和谷歌官方给出的方案就是Auto Layout、LinearLayout等“声明式”布局逻辑,而应对客户端动态化的方案,人们自然而然会想到Web的HTML+CSS,以及CSS的超频版本Flexbox和Gridlayout等等。

锚点及其应用

纯代码布局在表达上所遇到的困境本质是,系统原生接口暴露的view.origin.x, view.origin.y, 与开发过程中所表达的诸如“居中对齐”, “左对齐”, “底对齐”等需求描述语义存在这小小的距离。而通过对视图增加锚点概念:

1. left
2. right
3. top
4. botom
5. centerX
6. centerY

7. width
8. height

则消灭了需求和本质实现的语义差距,于是居中逻辑表述变成了:

view.centerX = bgView.width / 2
view.centerY = bgView.height / 2

类似的“左对齐”:

view2.left = view1.left

“底对齐”:

view2.bottom = view1.bottom

这样一来,布局需求的描述和布局实现如出一辙,容易编写,容易维护。锚点的概念让代码布局逻辑简洁清晰,使纯代码布局成为可能甚至成为优选方案。

开发效率

我们认为,对于关键技术和框架的选择,开发效率的考虑尤为重要。总结下来,下面两点非常关键: 1. 即时反馈的编程环境 2. 简洁强力的语义表达

即时反馈的编程环境

  • IB & Playground

  • Live Reload

  • IntelliSense

我们看到这些编程环境的特点: 1. 即时看到执行结果 2. 即时看到执行过程(中间步骤结果) 3. 即时的反馈可用接口 实际上前端开发同学之所以能在一个拙劣混乱的基础架构上达成较高的开发效率,很大程度上收益于前端开发很容易搭建一个即时反馈的编码环境,尤其是Live Reload支持,对生产力提升尤为明显,甚至弥补了语言和良好IDE支持的缺陷。这其中动态的能力是关键,但并不代表Web是客户端动态化唯一的方案。综合利弊,我们可以吸取三端编程实践最好的东西,构建最富有生产力的客户端动态化框架。譬如,picasso利用JavaScript的动态特性, 实现了Live Reload,又借助TypeScript+Visual Studio Code+Babel达到了传统客户端静态类型语言的开发体验,提高生产率的同时,还从工程上保证了可靠性。

简洁强力的语义表达

我们这里使用一个简单的例子,关于水平方向上约束的表达对比。

首先我们看到,官方使用伪代码的形式示意说明约束表达的含义,显然伪代码肯定是广大人民群众喜闻乐见的形式:

RedView.Leading = 1.0 x BlueView.trailling + 8

然后就面临这血淋淋的现实,模式的约束构造方式是这样的:

[NSLayoutConstraint constraintWithItem:blueView
                             attribute:NSLayoutAttributeTrailing
                             relatedBy:NSLayoutRelationEqual
                                toItem:redView
                             attribute:NSLayoutAttributeLeading
                            multiplier:1
                              constant:8];

很显然,没人原意去写这样的代码,于是GitHub上涌现了大量的基于autolayout的DSL,选一个其中的翘楚:Catography(Swift)来说明,写出来的代码是这样的:

layout(blueView, redView) { blueView, redView in
    redView.leading == blueView.trailing + 8
}

如果能选择性的忽略layout函数调用,block体里面的约束表达已经和伪代码描述如出一辙了,最后我们看下匠心布局的写法是什么?

redView.left = blueView.right + 8

对比下来匠心布局的表达是秒杀对手的,实际上匠心布局只做了最简单的一层抽象,毫无疑问是稳定性和执行效率更高的方案。 配合屈指可数的数个锚点,匠心布局代码无异于表达布局需求的伪代码,这样的代码具有最高的可维护性,显然对提高生产力有着巨大的好处。

picasso

picasso(毕加索)picasso是我们基于上述的匠心布局理念开发出的跨平台动态化布局框架。它有如下几个优点:- 配合100%的单元测试,picasso核心引擎稳定性值得信赖; picasso提供开放而统一的扩展体系,对控件的扩展支持只需要实现一个简单的wrapper,甚至丝毫不侵入自定义控件的实现。

简单描述一下picasso框架的基本原理。picasso接受使用匠心布局实现布局编码逻辑的js文件和和业务数据,在JSCore中执行JavaScript逻辑,并输出页面视图树的中间表示PicassoModel,而后由picasso引擎构建出不同平台的视图树。其中picasso计算的过程可以在手机后台线程完成,并且picasso在进行视图树构建的时候可以做极致的性能优化,可以认为picasso实现的视图可以有比原生代码实现更好的流畅度。

除了picasso核心引擎,picasso也是客户端动态布局一整套最佳实践总结。picasso-server提供了完整的Live Reload编程体验,推荐使用的VSCode+TypeScript+Babel的组合,带来的是客户端原生编程的安全性和便利性;同时,picasso-server还提供了项目和单文件的脚手架功能;随着picasso布局的深入使用,picasso将汇总广泛丰富的最佳实践,更进一步的促进生产力的提升。

显然热部署能力是现阶段各个业务对于picasso的“刚需”,即使放弃使用picasso的热部署能力,picasso解决方案所提供的Live Reload、跨平台、简洁强力的布局DSL,都能大大提升开发效率。 预测未来最好的方式就是创造未来,毕加索(picasso)就是我们创造出的布局编码的未来。

大为,美团点评公司点评平台iOS负责人,资深布局师,主导研发包括piccaso在内的一系列基础框架和基础设施,有效支撑了美团点评移动业务的蓬勃发展。

点评平台移动技术团队,同时肩负了平台性业务研发和基础框架的研发工作,在支撑美团点评业务高速发展的同时,积淀了一系列的基础框架、研发技术流程等方面的最佳实践,未来期待和业界有更多交流。