共计 5197 个字符,预计需要花费 13 分钟才能阅读完成。
图片来自:https://unsplash.com/photos/m…
本文作者:旭风
背景
一款社交产品的诞生,离不开即时通讯(IM)场景。随着团队业务幅员在社交畛域的布局,诞生了多个社交场景 APP,波及的 IM 场景,蕴含私聊、群聊、聊天室等。
这些 IM 场景,在音讯流的展现模式上是极为类似的,同时每个业务又有着本人非凡的交互需要。基于此,咱们对 IM 音讯流能力做了标准化的构建,来缩小 IM 性能的业务接入老本;同时也是为了对立各个业务的技术计划,缩小跨业务开发的了解和保护老本。本文次要针对 iOS 端在 IM 音讯流交互层的设计上,提供一些实际思路。
业界计划
目前业界有各种即时通讯服务商(例如云信、LeanCloud 等)提供的配套交互层解决方案,其大多以就义灵活性来满足疾速集成须要,在定制能力上远不能胜任咱们业务须要。再者则是诸如 MessageKit
之类的社区 IM 框架,其在视觉交互体现上性能齐备,能帮忙咱们疾速、灵便搭建音讯流构造,但业务须要的是一套残缺的携带音讯交互能力的计划,因而对此类框架,仍须要做不小的革新能力适应咱们的业务。
思考
对于一个音讯流交互层计划,次要思考几个方面:
- 标准的音讯流构造 :提供音讯流视图构造规范化的构建形式
- 规范的音讯交互能力 :对立音讯交互能力,业务方按需应用,疾速集成
- 业务拓展性 :针对数据源、音讯交互能力提供业务灵便拓展点
- 业务接入老本 :内置通用交互计划,升高业务接入老本
目前,咱们存量业务中的 IM 场景,底层 IM 能力次要由云信引擎提供。同时又存在基于业务服务端,通过 HTTP 去交互的场景。另外,还须要预留前期切换 IM 引擎的可能性,因而须要将交互层 IM 能力形象进去。此外,为了适应团队现状,减小业务接入老本,思考将云信提供的交互能力内置在计划中。
整体设计
设计愿景:提供标准化的能力,同时对拓展凋谢。
咱们冀望一套通用的音讯流能力,可能在计划上标准化。这里的标准化,次要蕴含音讯流构造构建的标准化,以及音讯交互能力的标准化。同时,计划须要在交互能力上适应不同业务场景,因而采纳依赖注入的形式,提供业务定制能力。依照职能划分,将框架整体分为了两层:
- 音讯流结构层:负责音讯流构造的构建,定义音讯视图、布局、数据上的标准,提供业务层别离在「音讯」、「会话」两个维度的配置能力。
- 音讯交互层:提供音讯能力、音讯流、音讯数据方面的交互能力,向下依赖交互接口,内置规范交互能力的同时,也反对业务按需注入交互实现。
流构造
音讯组件
不同的业务场景,音讯流款式体现必然有所差别。上面列出了咱们几个业务中的音讯流界面:
如何设计一套通用的音讯流视图构造,满足不同业务须要?通过对各个业务以及一些支流 IM 工具的察看,将音讯视图结构设计成如下构造,是可能满足咱们各个 IM 场景须要的:
我将音讯构造拆分成了 5 局部,对应 5 个音讯组件 MessageView
,每个音讯组件都反对业务对其「款式」、「显隐」、「布局」进行配置,从而满足不同场景定制须要。
MessageView
作为根底音讯组件,提供了一些规范能力,例如是否响应菜单动作 canPerformMenuAction
、视图重用回调机会 prepareForReuse
、尺寸策略等。
open class MessageView: MessageAbstractView {
public var canPerformMenuAction = false
open func refresh(with message: Message) {}
open func prepareForReuse() {}
open class func createSizeStrategy(message: Message, fittingSize: CGSize) -> MessageLayoutSizeStrategy? {// ...}
}
尺寸策略
音讯组件尺寸作为音讯流布局上不可或缺的因素,计划提供了多种尺寸计算策略 MessageLayoutSizeStrategy
:
- 主动布局计算策略:业务方对音讯组件应用 AutoLayout 布局时应用,外部会根据束缚主动计算好组件尺寸
- SizeThatFit 策略:根据组件
SizeThatFit
办法返回的尺寸进行布局 - 自定义策略:提供自定义尺寸计算形式
public protocol MessageLayoutSizeStrategy {
func caclulateSize(_ sizeViewType: MessageView.Type,
message: Message,
fittingSize: CGSize) -> CGSize
}
public struct MessageAutoLayoutSizeStrategy: MessageLayoutSizeStrategy {
public func caclulateSize(_ sizeViewType: MessageView.Type,
message: Message,
fittingSize: CGSize) -> CGSize {
// ... 省略其余代码
return sizeView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
}
}
public struct MessageSizeThatFitsStrategy: MessageLayoutSizeStrategy {
public func caclulateSize(_ sizeViewType: MessageView.Type,
message: Message,
fittingSize: CGSize) -> CGSize {
// ... 省略其余代码
return sizeView.sizeThatFits(fittingSize)
}
}
布局快照
咱们还针对音讯组件维度反对了布局快照。通常当一个音讯组件尺寸固定,在交互过程中尺寸不会产生的状况下,关上布局快照,以缩小布局计算耗费。同时也提供了快照革除的能力。咱们对多个音讯流在疾速滚动过程中的 CPU 峰值做了统计,在应用主动布局尺寸策略的状况下,开启布局快照,峰值升高了 10%~20%。
交互事件
另外在手势交互上,对外裸露了各个音讯组件的一系列交互事件。常见的场景例如单击浏览音讯内容,长按展现音讯菜单等。计划外部提供了基于零碎款式的长按菜单,并提供下层菜单配置能力,同时也能够基于裸露的长按手势事件来自定义菜单。
流
一个会话对应一个流,计划也提供了音讯流在会话维度上的一些标准化配置。例如音讯分页数量、是否主动拉取历史音讯、是否开启增量刷新,以及在工夫展现上的款式配置等。
此外为了缩小列表重绘,音讯流也反对增量刷新。通常状况下业务层不须要被动刷新列表,只需对音讯数据进行增删改操作,外部会触发对数据源的「diff-update」计算,从而驱动列表的增量更新。
交互层
对于业务方而言,在音讯交互上通常关怀这么几点:
- 提供了哪些标准化的交互能力
- 如何拓展自定义的交互实现
- 如何对交互流程进行干涉
联合团队现状,咱们在计划外部内置了基于云信的 IM 交互能力,同时定义了相干交互接口,供业务方按需注入实现。在理论业务中,一个 APP 内可能存在多个 IM 场景,因而交互能力反对按会话维度进行注入,各个会话之间的交互是互相隔离的。
音讯源
不同的 IM 场景,音讯数据起源可能存在差别。例如咱们私聊、群聊的数据源来自云信数据同步服务,聊天室数据须要通过云信提供的历史音讯接口拉取,另外也存在诸如通过业务服务端接口来拉取音讯数据的场景。因而计划上设置了数据源接口 SessionMessageProvider
,提供不同场景音讯源的定制能力。
public protocol SessionMessageProvider {
func messages(in session: Session,
anchorMessage: Message?,
limit: Int,
completion: @escaping ([Message]) -> Void)
}
计划设置了一个负责管理音讯数据源的 DataManager 实例,其依赖 SessionMessageProvider
提供的数据源。同时内置了基于云信的数据源获取实现,可能依据以后会话类型,获取私聊、群聊、聊天室的数据源。如果以后场景是通过 HTTP 拉取音讯的,则须要业务下层手动注入一个从接口获取数据源的 SessionMessageProvider
实例。
交互源
计划提供了 IM 规范交互能力,例如音讯收发、音讯撤回、保留等,以对立各业务交互姿态。具体的交互源除了要思考目前蕴含的云信及业务服务端,也要适应其余交互源,因而将交互实现局部也形象出了接口 MessageServiceInterface
。业务依据以后理论场景,注入具体的交互实现即可。上面列出了一些交互申明:
public protocol MessageServiceInterface {func send(message: Message, in session: Session, completion: @escaping MessageServiceInterfaceCompletion)
func resend(message: Message, completion: @escaping MessageServiceInterfaceCompletion)
func forward(message: Message, to session: Session, completion: @escaping MessageServiceInterfaceCompletion)
func revoke(message: Message, completion: @escaping MessageServiceInterfaceCompletion)
func save(message: Message, in session: Session, completion: @escaping MessageServiceInterfaceCompletion)
func delete(message: Message, completion: @escaping MessageServiceInterfaceCompletion)
}
同样,咱们也内置了一些通用交互计划,例如反对云信提供的私聊群聊交互能力,以及由中台提供的通用聊天室服务交互能力,以反对相干场景下疾速接入。
交互钩子
在理论 IM 业务开发过程中,往往须要对交互流程做一些干涉,或是在交互过程中做一些定制化的动作。因而计划也提供了一些交互钩子,反对「交互前置校验」、「交互前筹备」。以音讯发送流程为例,提供了「发送前校验」、「发送筹备」两个音讯发送过程的回调钩子:
public protocol MessageServicePrechecker {
// 音讯发送前置校验
func shouldSend(message: Message, in session: Session) -> Bool
// ... 省略其余代码
}
public protocol MessageServicePreparation {
/// 筹备发送筹备
func prepareSend(message: Message, in session: Session, callback: @escaping MessageServicePreparationCallback)
// ... 省略其余代码
}
整体的发送流程如图所示:
前置校验阶段,用来作音讯发送前的校验工作,依据理论状态决定音讯是否能够发送。发送筹备阶段,则能够在音讯投递前做最初的筹备工作,例如海内业务能够在这里解决音讯资源附件上传 Amazon,或是在此处对音讯塞入一些客户端信息、反作弊 Token 等,反对异步操作。
业务接入
业务只须要在下层提供针对音讯以及会话两个维度的配置,就能基于内置的交互能力,构建出一套根底的 IM 音讯流能力。在具体的音讯款式出现上,则通常须要业务层保护一组对于「音讯类型 - 音讯组件类型 - 音讯构造」的映射关系,具体关联如下:
在交互能力上,提供了 IM 场景的规范能力,业务能够按需应用。
另外,理论 IM 场景可能须要一些更为丰盛的定制能力,则能够根据计划提供的音讯数据源接口、音讯交互接口来对具体交互实现进行定制。同时也能够应用相干的交互钩子对交互过程进行干涉,以适应本人的业务。
总结
本文对团队 IM 场景的现状做了简略介绍,撇开具体实现细节,就如何搭建一套可能适应多业务须要的通用 IM 音讯流交互层计划,提供了一些思考和实践经验。从后果来看,该计划稳固撑持了团队多个 IM 场景,抹除各场景实现差别,无效升高了保护老本和新业务接入老本。
本文公布自网易云音乐技术团队,文章未经受权禁止任何模式的转载。咱们长年招收各类技术岗位,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!