背景
目前探探 IM 聊天消息列表由于长年累月的代码堆积,对业务迭代产生了很多的困扰。所以趁着工作中的一些空隙,对聊天页消息卡片做了插件化,使得不同的消息类型,可以根据具体需求方便的增删迭代。下面分享一下自己重构过程中一些有趣的想法。虽然目前是在聊天消息列表中进行实践的,但对于各种复杂 Feed 流业务也有一定的借鉴意义。
此次插件化改造使用了 IGListKit,这是一个优秀的数据驱动 UICollectionView 展示的框架,有不少思路也是从这个框架里的一些设计中得到了灵感,有兴趣可以找一些文章看看。当然 IGListKit 也不是必要的,是否使用它也不影响我们的设计。
聊天消息列表可以泛化为一个有较多不同复杂业务逻辑卡片的 Feed 列表,不过聊天消息列表还是有不少独特的内容,比如涉及数据库存储读取,消息收发,实时更新等等,不过在本文中我们暂时忽略这些内容。这里先立个 flag,有时间另外写一篇文章,来单独介绍 IM 消息数据的管理以及如何驱动消息列表展示的。希望这篇文章能够对大家日常开发中有一定的帮助,不足之处,敬请指正。
什么是插件
不少项目中,或多或少都存在一些插件化的设计,但是有不少插件化设计并不是太好,大概的原因是对于插件本质特征理解有偏差。比如在探探原有的「插件(Plugin)」定义就容易造成理解上的混淆,比如有些插件被移除,导致整个聊天系统不工作,这种东西就不应该定义为插件。
那么什么应该是插件呢,我想插件一定要符合这些特征:
- 插件是对于宿主功能的补充;
- 移除插件不应该影响宿主的核心功能;
- 插件与宿主代码上是正交的、互不影响的。
卡片插件化应用场景
下面简要描述一下当前的应用场景,目前探探 IM 聊天消息列表中流通着约 120+ 种消息,很多消息除了展示以外还有特殊逻辑,下面简单描述一下
- 点击跳转到特殊页面:比如邀请类消息、活动类消息,Feed 流里跳转详情等;
- 存在特殊功能按钮:比如投票类消息,卡片里有点赞按钮;
- 同步展示业务数据:比如消息里引用了一个有时效的状态,并且产品要求需要实时更新状态;
- 多消息之间的联动:比如切换播放多条音频消息,或者某条卡片展示的时候要更新其他卡片。
下面我们看一下如何设计架构,来优雅的支持这些需求
消息卡片 ViewModel 都有哪些内容
MessageCardViewModel 是负责携带消息卡渲染信息、以及逻辑处理工具的模型,消息 UI 数据源以及逻辑处理均通过 ViewModel 信息以及附属的工具。主要承载通过 message 解析完成后的静态内容;还会绑定一些具有逻辑处理能力的工具以及业务配置信息,各种不同消息可以通过 BaseMessageCardViewModel 来实现多态。
下面我们介绍一下 ViewModel 里都包含哪些内容,都涉及哪些类,这些类都是做什么的:
需要注意一下,为防止 ViewModel 在数据流传递的过程中被意外修改,对外所有属性都是 readonly 的,来保证数据流的数据生产到消费是单向的,下面是 ViewModel 所包含的具体内容
一、message 解析内容
message 解析内容是通过对消息原始数据,解析为消息卡片直接使用的数据,BaseMessageCardViewModel 包含 message 的原始数据,以及通过 message 解析而来可以直接用于兜底展示的属性,比如 displayedText 等。
其他消息可以通过继承 BaseMessageCardViewModel 来组装自己所需要的属性,比如 VideoMessageCardViewModel 可以通过继承,增加 videoInfo 属性,该属性也是通过 message 原始数据解析而来,对应的 UI 可以通过 v