luochao 2019-06-10
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设计了如下架构:
总体来说,这个架构采用标准的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逻辑解藕的目标。
基于以上的诉求,我们设计了如下的整体架构。以回话列表和消息列表为例,具体的架构设计如下图所示:
我们还是以会话列表和消息列表为例,具体阐述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 *)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath messageModel:(id)model { // do something}
- (void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(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移动端目前也在这个方向持续的优化和努力。