58即时通讯移动端架构的实践总结

58即时通讯(以下简称微聊,亦或简称IM SDK)作为58集团即时通讯的解决方案,承载了58同城、赶集网、安居客、移动经纪人、招才猫等商业产品线的用户在线沟通能力。目前支持iOS、Android、Web、H5等所有平台。为了满足58集团不断增长的业务需求,从设计之初到现在,微聊的架构经历了几个版本的演进,目前已经形成一套多平台接入、层次分明、结构清晰的客户端架构。

1.背景

在58集团的各业务线中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。此时需要较为完善的即时通信(IM)解决方案,但是由于58集团针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK项目便应运而生,APP通过接入SDK,可以快速实现IM基本功能。

2.整体设计

在最初的设计中,我们总结了如下的几个设计目标:

1)IM 主流程稳定可用:消息传输具有高可靠性。

2)UI 组件直接集成进入SDK,并支持可定制化。

3)富媒体发送集成进入SDK,并可按需定制需要的富媒体类型。

4)实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。

依据以上几个诉求,我们为IM SDK设计了如下架构:

名称

描述

C++ Net

负责SDK长链接相关的逻辑。包括登录、登出、互踢、推消息、心跳包等。

C++ Biz

负责SDK各个模块的数据逻辑,如会话列表、消息列表、用户资料、好有关系、群等。还同时支持持久化存储等功能。更重要的是该层是跨平台的,不仅能够支持移动端平台接入,还能满足Windows、Mac等PC端平台接入使用。

Lib层

平台中间层,为上层提供平台形式的交互接口。

UIKit层

Kit层不仅负责UI渲染、事件订阅、用户事件处理等常规内容,同时还负责拉取会话列表、消息列表上下翻页、消息补空洞、离线消息跳转、同步用户信息等即时通讯的业务逻辑。

总体来说,这个架构采用标准的MVC模式,使得在项目初期,产品得以快速上线发布。并且借助底层核心逻辑的跨平台优势,得以在各个开发平台快速部署,实现功能完善,平台覆盖全面的战略目标。

3.设计要点

此章节中主要描述了,IM SDK设计中一些重要流程。

3.1 消息发送流程

IM SDK目前支持但不限于文字,富文本,音视频、图片、附件、位置、交互卡片等多种形态的消息类型。并提供统一的发送接口,其发送流程主要如下图所示:

3.2 收消息流程

收消息在客户端存在二种场景,一种是在线收消息,一种是离线收消息。以下分别阐述具体过程。

3.2.1 客户端在线收消息

收消息的时候,需要将数据派发到单独的dispatch线程上处理解析、I/O等环节。然后回调业务方的消息监听接口,业务方需要针对UI的具体场景,做不同的处理。除了刷新消息列表,IM SDK还会回调业务方会话列表变更的监听接口,同时刷新会话列表。

3.2.2 客户端离线收消息

客户端退到后台60s以上,或者app处于未启动状态时候,客户端以push的方式收消息。push消息的处理流程如下图所示:

SDK通过同步会话列表的方式,将每个会话的最后一条push消息拉取到本地,当打开某一个聊天页的时候,才会将中间的其他消息补全。

3.3 可定制化的UI

随着公司业务规模的扩大与业务线的快速迭代,新的业务也需要IM即时通讯功能,众所周知,哪怕是IM即时通讯的UI功能,也会占据大量的开发与调试时间, 为了解决这个痛点,我们提供完整的IM即时通讯解决方案,包括Lib层(IM接口层解决方案)、UIKit层(UI层解决方案)、Logic层(可定制化的UI抽象层解决方案)等。

我们改造了原有UIKit层方案,将其拆分为Logic+UIKit的架构方式的初衷是:

1)IM即时通讯的UI逻辑有其共通性,具备以SDK的方式提供IM一揽子解决方案的前提条件。

2)我们希望把IM SDK的持续迭代收敛在SDK内部,不对业务方造成干扰。实现和业务方自身UI逻辑解藕的目标。

基于以上的诉求,我们设计了如下的整体架构。以回话列表和消息列表为例,具体的架构设计如下图所示:

描述

ChatVirtualView

聊天页的虚拟view,主要角色是处理聊天页列表的交互(包括但不限于上拉,下拉,消息底部判断,消息在屏幕的位置的定位等),该虚拟view会维护一个业务方的tableview实例对象,托管的tableview对象需要业务方在该类初始化的时候进行配置。配置之后tableview的代理实现和滚动回调会在这个虚拟view中进行处理,并根据情况回调给业务方。

ChatViewModel

处理和聊天页UI相关的数据逻辑,比如上UI的数据源、消息收敛策略、拉取历史消息的任务之间的同步、删除消息、撤回消息、已读未读状态等内容

TalkVirtualView

会话列表页的虚拟view,主要角色是处理会话列表页的交互,该虚拟view会维护一个业务方的tableview实例对象,托管的tableview对象需要业务方在该类初始化的时候进行配置。配置之后tableview的代理实现和滚动回调会在这个虚拟view中进行处理,并根据情况回调给业务方。

TalkViewModel

处理和会话列表页UI相关的数据逻辑,比如上UI的数据源、会话的增量更新策略、业务方插入非IM SDK会话、删除指定会话,获取指定会话等内容

我们还是以会话列表和消息列表为例,具体阐述Logic+UIKit这种架构方案的优势:

3.3.1 会话列表的UI定制化

3.3.1.1 会话列表的类结构介绍

会话列表在IM SDK中的入口是WChatConversationListController类。其具体的类结构如下所示:

类图解释:

(1) WChatConversationListController中拥有一个WChatLogic层WIMTalkVirtualView类的实例对象vv,并实现接口类WIMTalkVirtualViewDelegate,如图中左部分所示。

(2) vv以WChatConversationListController中的conversationListTableView为入参,并赋值给其属性conversationListTableView,并将TableView的delegate和dataSource设置为vv,相关处理逻辑封装在vv中。其中所需数据源为:

[WIMLogicTalkViewModelsharedInstance].conversationListDataSource

(3) WChatConversationListController创建vv示例代码如下:

self.vv = [[WIMTalkVirtualView alloc]initWithTableView:self.conversationListTableView];self.vv.delegate = self;

3.3.1.2 WIMTalkVirtualViewDelegate回调实现

由于TableView的回调在vv上,所以接口类WIMTalkVirtualViewDelegate透传了绝大多数TableView的回调方法给WChatConversationListController(如果不满足需求可以通过继承的方式自己扩展)。比如如下几个重要的代理方法:

(1) 绑定会话类型与Cell:

-(UITableViewCell*)tableViewCellForConversationVM:(WIMTalkVirtualView *)vvtableView:(UITableView *)tableview model:(id)model atIndexPath:(NSIndexPath*)indexPath;

(2)TableView Cell 点击回调:

-(void)conversationVM:(WIMTalkVirtualView*)vv didSelectConversation:(id)conversation;

(3)计算会话列表单元行高度:

-(CGFloat)calculateModelHeight:(GmacsConversationModel*)model;

3.3.2 消息列表的UI定制化

3.3.2.1 消息列表的类结构介绍

消息列表页面在IM SDK中的入口是WChatConversationController类。其类结构如下图所示:

类图解释:

(1) WChatConversationController中拥有一个WChatLogic层WIMChatVirtualView类的实例对象vv,并实现接口类WIMChatVirtualViewTableViewDelegate(透传TableView回调的协议)和WIMChatVirtualViewCommonDelegate(其他回调,如上拉开始/结束,下拉开始/结束,消息跳转,发/收消息前,发/收消息后,列表滚动事件等),如图中左部分所示。

(2) vv以WChatConversationController中的messageTableView为入参,并赋值给vv的属性messageTableView,并将TableView的delegate和dataSource设置为vv,相关处理逻辑封装在vv中。其中所需数据源为:

[[WIMLogicChatViewModelsharedInstance].messageList


(3) 如果SDK的消息模型不满足业务方的UI渲染需求,我们支持业务方定义自己的消息模型,方式是继承WIMChatVirtualView类,并在子类中实现-(Class)getMessageModelClass方法,返回业务方自己的消息模型,SDK将为业务方实例化该类作为消息模型对象来使用。

(4) WChatConversationController创建vv示例代码如下:


self.vv = [[WIMChatVirtualView alloc]initWithTableView:self.messageTableView startMessageLocalId:self.startMessageLocalIdtargetId:targetId targetSource:targetSource conversationType:conversationType];
self.vv.tableViewDelegate = self;self.vv.commonDelegate = self;

3.3.2.2 实现回调接口

(1)实现WIMChatVirtualViewTableViewDelegate协议。以下实现是IM SDK的demo部分接口示范代码,业务方根据自己业务逻辑酌情处理。


– (UITableViewCell*)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath
messageModel:(id)model {
// do something
}


– (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath*)indexPath
messageModel:(id)model {
// do something
}


– (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath
messageModel:(id)model {
// do something
}

(2)实现WIMChatVirtualViewCommonDelegate协议。以下实现是IM SDK的demo部分接口示范代码,业务方根据自己业务逻辑酌情处理。


/**
* 开始加载上一页动画
*/
– (void)startLoadBackwardsAnimation {
// do something
}

/**
* 开始加载下一页动画
*/
– (void)startLoadForwardsAnimation {
// do something
}

/**
* 加载上一页动画结束
*
* @param errorCode 错误码
* @param errorMsg 错误描述
*/
-(void)onLoadBackwardsFinished:(NSInteger)errorCode errorMsg:(NSString*)errorMsg {
// do something
}

/**
* 加载下一页动画结束
*
* @param errorCode 错误码
* @param errorMsg 错误描述
*/
-(void)onLoadForwardsFinished:(NSInteger)errorCode errorMsg:(NSString *)errorMsg{
// do something

}

4.总结

以上就是58集团IM SDK架构设计的实践。我们认为好的架构应该是随着业务的拓展而不断的自我进化,脱离于具体业务的架构设计并不是最优设计。58集团的业务特性是业务线多,每个业务线拥有独立账号体系,且不同账号体系之间需要互通。其次是商家分类多样,导致不同身份场景下的交互方式差异性大,与此同时IM SDK需要深度服务于各大业务线,满足业务线的各类即时通讯需求,所以IM SDK移动端对架构的易用性、扩展性、兼容性有十分苛刻的要求。同时我们还注意到,优秀的架构设计能极大程度的降低迭代成本,随着业务线版本的迭代,IM SDK移动端目前也在这个方向持续的优化和努力。