iOS 列表界面如何优雅实现模块化与动态化
来源丨知识小集
前言
去年做了一个小组件,前些时间考虑到项目中可能会大规模实施,完善简化后新开了一个 repo: YBHandyList[1]。
有些朋友抛出了 nimbus、IGListKit 等业界应用很广的库,前些时间网易工程师也推出了 M80TableViewComponent。理论上这些组件的原理大同小异,虽然它们各有优势,但却不太能满足笔者对架构清晰度的要求。
本文分析 YBHandyList 的应用价值,希望能解开一些朋友的疑惑。
业务痛点
iOS 界面开发中 UITableView / UICollectionView 的出场率极高,它们都是使用代理方法配置数据源,虽然这样的设计理念符合了单一职责原则,但在列表变得复杂时代理方法的处理将变得力不从心:
• 同一个 Cell / Header / Footer 处理逻辑分散在各个代理方法中,不便于管理。
• 当列表数据动态变化时,每一个代理方法里的判断逻辑都将变得复杂,且这些逻辑很可能会相互关联。
显然,在这样的场景下将是维护的灾难,特别是当你接手别人的代码发现每个 UITableView 代理方法里都有几十个if-else,它们人多势众,量你不敢动它们任何一个。
由此可见,若想维护性高需要解开每一个 Cell 之间的逻辑耦合,也就是通常意义的模块化,由此才能更轻易的实现动态化。解决方案其实很简单,只需要一个中间类,将分散的配置集中起来(在代理方法里取这个中间类的对应值):
然而对于业务工程师来说,每次写这样的代码都意味着时间成本,所以制作一个基础组件是很有必要的,它需要满足以下特性:
• 模块化配置 Cell / Header / Footer。
• 更容易实施列表动态化。
• 能拓展原生能实现的所有场景。
为此,YBHandyList[1] 应运而生,它足够简单以至于从设计到编码基本就花了一天时间。
YBHandyList 的优势
原理:
代码简单轻量
YBHandyList 保留最小功能,代码量很少,核心思路就一句话:将 UITableView / UICollectionView 的数据源从代理方法配置转化为数组配置。
在其它库当中可以看到高度缓存、访问迭代器等逻辑,笔者认为这样的基础设施不应该侵入过多业务,它们本应该是业务关注的逻辑,这样的语法糖只能在简单场景下少写些代码,当业务变得复杂时往往这样的优势就不存在了。
YBHandyList 的语法糖非常收敛,简单的一个延展,你甚至可以选择不使用语法糖,直接使用代理实现类。
由此,新手工程师也能对实施代码充满信心。
业务侵入性低
YBHandyList 采用 IOP 设计,最大限度的降低了业务侵入性,只需要在 Cell / Header / Footer 中实现几个代理方法就行了。
去基类化设计让数据流动过程更加纯粹,不需要考虑父类做了什么,没做什么。
在老业务中可能存在类似BaseTableViewCell 的东西,YBHandyList 也能优雅的接入,这种场景下继承的设计范式将力不从心。
这种架构规范类组件接入的成本非常重要,而舍弃的成本也不容忽视,由于 IOP 天然的优势,YBHandyList 结构代码的舍弃将轻而易举,不拖泥带水。
直观的动态化控制
构建界面只需要关注所有id<Config>在数据源数组中的顺序,就像搭积木一样拼接起来,数组中的顺序就是对应 Cell 在界面中的显示顺序,由此就能通过改变数据源数组的顺序轻易的实现动态化控制。
在 MVVM 架构中实施
YBHandyList 的设计方式让它在各种架构中都能无障碍实施,下面以 MVVM 举例(仅说明 UITableViewCell 的实施,具体可以看 DEMO[1]:
可以看到,Cell 与 UITableView 非直接耦合,所以若需要将 Cell 的事件传递出来最好通过 Cell 的 ViewModel,ViewModel 作为连接 Cell 与外界的桥梁。
Cell 的 ViewModel 也可以在主 ViewModel 中构建,这样 Controller 中就不用导入这些类,不过当 Cell 的 ViewModel 需要将事件传递到 Controller 时,就会需要一些胶水代码通过主 ViewModel 间接传递。
数据绑定并非必须做的事情,你可以用 RAC,或者另外一个选择:EasyReact,可以参考笔者的文章:美团 EasyReact 源码剖析:图论与响应式编程[2]。
更安全和优雅的复用
很多时候,我们会将具体业务的处理逻辑放 Cell 中或者其 ViewModel 中,那么它们就很难复用,因为复用是建立在无具体业务侵入的前提下。
实际上只需要将具体业务的处理逻辑抽离出来,处理过后再放在 ViewModel 中,Cell 拿到 ViewModel 再进行具体业务无关的界面刷新。如此,ViewModel 将可以在任何地方复用。
使用 YBHandyList 后,ViewModel 把 Cell 与外部业务解开耦合,只把需要暴露的东西写在ViewModel .h中,外部业务无需导入 Cell 便能通过 ViewModel 直接复用,更加的安全。
能拓展原生支持的场景
一个基础设施最怕的就是不能满足所有场景的情况下还封闭了拓展的入口。YBHandyList 通过继承默认代理实现类就能拓展实现其它的 UITableView / UICollectionView 代理方法。
这看起来有些繁琐,使用多代理技术能避免额外的创建代理实现类,但这样会导致代码不再简单和透明。换个角度想,代理实现类中将大量复杂逻辑处理过后,仅仅回调给外部业务一个简单的方法,达到为外部模块瘦身的目的。
后语
笔者一直偏好简洁的代码设计,让核心功能最小化实现,当它无法覆盖所有的场景时一定要有原生拓展能力。语法糖的主要意义是减少使用者的思考成本而不单单是为了少写两句代码,它不应该侵入功能收敛的核心代码。要做好这一切,就一定要透过现象看清问题的本质。