图片来自:unsplash.com/photos/mw6O…

本文作者:旭风

背景

一款交际产品的诞生,离不开即时通讯(IM)场景。随着团队事务版图在交际领域的布局,诞生了多个交际场景APP,触及的IM场景,包括私聊、群聊、聊天室等。

这些IM场景,在音讯流的展现形式上是极为相似的,一起每个事务又有着自己特别的交互需求。依据此,咱们对IM音讯流才干做了规范化的构建,来削减IM功用的事务接入本钱;一起也是为了一致各个事务的技能计划,削减跨事务开发的了解和保护本钱。本文首要针对iOS端在IM音讯流交互层的规划上,供给一些实践思路。

业界计划

现在业界有各种即时通讯服务商(例如云信、LeanCloud等)供给的配套交互层解决计划,其大多以献身灵敏性来满意快速集成需求,在定制才干上远不能胜任咱们事务需求。再者则是比如 MessageKit 之类的社区IM结构,其在视觉交互体现上功用完备,能协助咱们快速、灵敏建立音讯流结构,但事务需求的是一套完好的带着音讯交互才干的计划,因而对此类结构,仍需求做不小的改造才干习惯咱们的事务。

考虑

关于一个音讯流交互层计划,首要考虑几个方面:

  1. 规范的音讯流结构:供给音讯流视图结构规范化的构建办法
  2. 规范的音讯交互才干:一致音讯交互才干,事务方按需运用,快速集成
  3. 事务拓宽性:针对数据源、音讯交互才干供给事务灵敏拓宽点
  4. 事务接入本钱:内置通用交互计划,降低事务接入本钱

现在,咱们存量事务中的IM场景,底层IM才干首要由云信引擎供给。一起又存在依据事务服务端,经过HTTP去交互的场景。别的,还需求预留后期切换IM引擎的或许性,因而需求将交互层IM才干笼统出来。此外,为了习惯团队现状,减小事务接入本钱,考虑将云信供给的交互才干内置在计划中。

全体规划

规划愿景:供给规范化的才干,一起对拓宽开放。

咱们希望一套通用的音讯流才干,能够在计划上规范化。这里的规范化,首要包括音讯流结构构建的规范化,以及音讯交互才干的规范化。一起,计划需求在交互才干上习惯不同事务场景,因而选用依靠注入的办法,供给事务定制才干。 依照功能区分,将结构全体分为了两层:

社交场景下iOS消息流交互层实践

  • 音讯流结构层:担任音讯流结构的构建,界说音讯视图、布局、数据上的规范,供给事务层分别在「音讯」、「会话」两个维度的装备才干。
  • 音讯交互层:供给音讯才干、音讯流、音讯数据方面的交互才干,向下依靠交互接口,内置规范交互才干的一起,也支撑事务按需注入交互完成。

流结构

音讯组件

不同的事务场景,音讯流款式体现必定有所差异。下面列出了咱们几个事务中的音讯流界面:

社交场景下iOS消息流交互层实践

怎么规划一套通用的音讯流视图结构,满意不同事务需求?经过对各个事务以及一些干流IM东西的调查,将音讯视图结构规划成如下结构,是能够满意咱们各个IM场景需求的:

社交场景下iOS消息流交互层实践

我将音讯结构拆分成了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

  1. 主动布局核算战略:事务方对音讯组件运用 AutoLayout 布局时运用,内部会依据约束主动核算好组件尺度
  2. SizeThatFit 战略:依据组件 SizeThatFit 办法回来的尺度进行布局
  3. 自界说战略:供给自界说尺度核算办法
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%。

社交场景下iOS消息流交互层实践

交互事情

别的在手势交互上,对外暴露了各个音讯组件的一系列交互事情。常见的场景例如单击阅读音讯内容,长按展现音讯菜单等。计划内部供给了依据系统款式的长按菜单,并供给上层菜单装备才干,一起也能够依据暴露的长按手势事情来自界说菜单。

一个会话对应一个流,计划也供给了音讯流在会话维度上的一些规范化装备。例如音讯分页数量、是否主动拉取前史音讯、是否开启增量改写,以及在时刻展现上的款式装备等。

此外为了削减列表重绘,音讯流也支撑增量改写。通常情况下事务层不需求主动改写列表,只需对音讯数据进行增修改操作,内部会触发对数据源的「diff-update」核算,从而驱动列表的增量更新。

社交场景下iOS消息流交互层实践

交互层

关于事务方而言,在音讯交互上通常关心这么几点:

  1. 供给了哪些规范化的交互才干
  2. 怎么拓宽自界说的交互完成
  3. 怎么对交互流程进行干涉

结合团队现状,咱们在计划内部内置了依据云信的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 实例。

社交场景下iOS消息流交互层实践

交互源

计划供给了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)
}

相同,咱们也内置了一些通用交互计划,例如支撑云信供给的私聊群聊交互才干,以及由中台供给的通用聊天室服务交互才干,以支撑相关场景下快速接入。

社交场景下iOS消息流交互层实践

交互钩子

在实践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)
    // ...省掉其他代码
}

全体的发送流程如图所示:

社交场景下iOS消息流交互层实践

前置校验阶段,用来作音讯发送前的校验作业,依据实践状况决议音讯是否能够发送。发送预备阶段,则能够在音讯投递前做最终的预备作业,例如海外事务能够在这里处理音讯资源附件上传Amazon,或是在此处对音讯塞入一些客户端信息、反作弊Token等,支撑异步操作。

事务接入

事务只需求在上层供给针对音讯以及会话两个维度的装备,就能依据内置的交互才干,构建出一套根底的IM音讯流才干。在详细的音讯款式出现上,则通常需求事务层保护一组关于「音讯类型-音讯组件类型-音讯结构」的映射联系,详细关联如下:

社交场景下iOS消息流交互层实践

在交互才干上,供给了IM场景的规范才干,事务能够按需运用。

别的,实践IM场景或许需求一些更为丰厚的定制才干,则能够依据计划供给的音讯数据源接口、音讯交互接口来对详细交互完成进行定制。一起也能够运用相关的交互钩子对交互进程进行干涉,以习惯自己的事务。

总结

本文对团队IM场景的现状做了简单介绍,放下详细完成细节,就怎么建立一套能够习惯多事务需求的通用IM音讯流交互层计划,供给了一些考虑和实践经验。从成果来看,该计划安稳支撑了团队多个IM场景,抹除各场景完成差异,有效降低了保护本钱和新事务接入本钱。

本文发布自网易云音乐技能团队,文章未经授权制止任何形式的转载。咱们终年接收各类技能岗位,如果你预备换作业,又刚好喜爱云音乐,那就参加咱们 grp.music-fe(at)corp.netease.com!