乐趣区

关于ios:社交场景下iOS消息流交互层实践

图片来自:https://unsplash.com/photos/m…
本文作者:旭风

背景

一款社交产品的诞生,离不开即时通讯(IM)场景。随着团队业务幅员在社交畛域的布局,诞生了多个社交场景 APP,波及的 IM 场景,蕴含私聊、群聊、聊天室等。

这些 IM 场景,在音讯流的展现模式上是极为类似的,同时每个业务又有着本人非凡的交互需要。基于此,咱们对 IM 音讯流能力做了标准化的构建,来缩小 IM 性能的业务接入老本;同时也是为了对立各个业务的技术计划,缩小跨业务开发的了解和保护老本。本文次要针对 iOS 端在 IM 音讯流交互层的设计上,提供一些实际思路。

业界计划

目前业界有各种即时通讯服务商(例如云信、LeanCloud 等)提供的配套交互层解决方案,其大多以就义灵活性来满足疾速集成须要,在定制能力上远不能胜任咱们业务须要。再者则是诸如 MessageKit 之类的社区 IM 框架,其在视觉交互体现上性能齐备,能帮忙咱们疾速、灵便搭建音讯流构造,但业务须要的是一套残缺的携带音讯交互能力的计划,因而对此类框架,仍须要做不小的革新能力适应咱们的业务。

思考

对于一个音讯流交互层计划,次要思考几个方面:

  1. 标准的音讯流构造 :提供音讯流视图构造规范化的构建形式
  2. 规范的音讯交互能力 :对立音讯交互能力,业务方按需应用,疾速集成
  3. 业务拓展性 :针对数据源、音讯交互能力提供业务灵便拓展点
  4. 业务接入老本 :内置通用交互计划,升高业务接入老本

目前,咱们存量业务中的 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

  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%。

交互事件

另外在手势交互上,对外裸露了各个音讯组件的一系列交互事件。常见的场景例如单击浏览音讯内容,长按展现音讯菜单等。计划外部提供了基于零碎款式的长按菜单,并提供下层菜单配置能力,同时也能够基于裸露的长按手势事件来自定义菜单。

一个会话对应一个流,计划也提供了音讯流在会话维度上的一些标准化配置。例如音讯分页数量、是否主动拉取历史音讯、是否开启增量刷新,以及在工夫展现上的款式配置等。

此外为了缩小列表重绘,音讯流也反对增量刷新。通常状况下业务层不须要被动刷新列表,只需对音讯数据进行增删改操作,外部会触发对数据源的「diff-update」计算,从而驱动列表的增量更新。

交互层

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

  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 实例。

交互源

计划提供了 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!

退出移动版