Global IM UIKit 选用“一键开关、二级重写、三级自界说”来扩大自界说才能,提升产品集成灵敏性,更好支撑事务层立异。

一键开关: 关于通用的、可枚举的交互和 UI 自界说规模,Global IM UIKit 内部现已帮开发者完成了相关功用和适配,只需一行代码,即可完成多种交互和 UI 的款式切换。

二级重写: 支撑 Module(页面)和 Component(组件)重写,包括 ViewModel、ViewController、View 的承继与重写。适用于对默认完成办法的极度个性化定制,扩展性极高。

三级自界说: 针对通用的、不可枚举的自界说规模,供给 API 自界说才能,例如:导航栏的操作项、输入框扩展功用进口等。

本文将以 iOS 端集成为例,分享 Global IM UIKit 中组件的灵敏拆分和高扩展性的规划思路,以及快速集成的实战教程。


首要功用和中心架构

在即时通讯组件中,首要的功用可聚合为两大页面:

会话列表: 以会话目标为元素,展现最近的会话目标,也是进行会话的首要进口。Global IM UIKit 针对会话项目支撑左滑修正和右滑修正,供给置顶、免打扰、符号已读等操作。

灵敏易用的即时通讯组件规划思路和最佳实践

会话: 以音讯目标为元素,展现一切音讯目标,也是一切音讯的建议进口。Global IM UIKit 中常用的音讯类型有:文本、图片、视频、文件、语音、贴纸等,支撑回复、转发、仿制等常用快捷交互。

灵敏易用的即时通讯组件规划思路和最佳实践

除以上两大页面之外,还有许多其他衍生页面,如:多媒体、预览、用户列表、用户信息等。当然,作为国际化产品,多言语和主题也是不可少的。

在 Global IM UIKit 中,尽管中心页面结构相对简略,但其交际功用聚合度十分高。尤其是在会话页面中,音讯的接纳、发送、操作等,不仅仅是数据和 UI 的交互,还涉及音讯的时序、安全、性能等,以及 SDK 的定制化需求。

运用传统的 MVC 结构会撑爆 Controller,给研发和维护产生许多后患。所以,融云 Global IM UIKit 选用 MVVM 架构,将音讯数据处理与 UI 烘托和交互合了解耦,下降代码逻辑杂乱度,合作灵敏的组件拆分,提高扩展性。

MVVM 也是一个普及度高、承受度好的结构,开发者能够快速上手。以会话列表和会话的对外接口类为例展现如下图:

灵敏易用的即时通讯组件规划思路和最佳实践


UI 组件

Global IM UIKit 的一个页面对应一个 Module,Module 中包括一个或多个 Component,并办理这些 Component 的全体布局。

例如,整个会话页面是一个 Module,其又能够被划分为三个 Component,分别是:顶部导航栏、底部输入组件和中心的滚动区。

在 UI 组件中,PaaS 服务的难点在于:需求考虑与各种事务场景的适配,经过高可重用的组件和高开放性的接口,尽可能多地支撑定制化需求。

以“底层精简+让渡自在”为产品规划中心思路,Global IM UIKit 将 IM 会话页面 Module 和各组件 Component 的重写自在给到开发者,开发者可灵敏自界说而不影响可用性。

为完成这一点,Global IM UIKit 的会话页面需按事务逻辑拆分成独立功用组件。每个模块能够作为重用单元,有助于解耦,下降体系的杂乱性,一起隔离也有助于提升开发效率。

在模块中全体选用 MVVM 的规划模式,关于功用较简略的组件仍然选用了 MVC 的规划模式。 当组件没有杂乱的交互和事务逻辑时,运用 MVC 会愈加简洁明了。

多言语和主题作为全局性的功用,会渗透到每个组件之中。在规划组件时,需求为多言语和主题的一键切换才能做衬托。

组件拆分

依据列表页面显现,能够划分为导航条(Header)、列表(List)、工具栏(ToolBar)和会话输入框(Input),如下图示:

灵敏易用的即时通讯组件规划思路和最佳实践

组件中会依据功用聚合再次拆分为更细粒度的组件,例如:输入框的 Reference、TextInput、Sticker、Recorder 等子组件。

灵敏易用的即时通讯组件规划思路和最佳实践

这样,Module 与 Component 的根本组件联系就整理拆分结束了。开发者能够用承继 Module,新增或修正 Component 的办法,完成组件的二级重写才能。

Component 就是 Global IM UIKit 模块化界说中的最小组成单元,也是开发者能够重写的最小单元。一起,重写的组件能够在上一级组件中,经过 setter 办法,设置为自界说组件。

组件完成

Global IM UIKit 选用 Objective-C 言语开发,一切组件依据原生结构完成。

组件中的颜色和图片,运用体系的动态接口,对应当时设置的主题:

/// 依据主题切换颜色
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider;
/// 为 Image 注册不同主题对应的图片,依据主题切换
- (void)registerImage:(UIImage *)image withConfiguration:(UIImageConfiguration *)configuration;

组件中的文字是经过本地化后的:

/// 体系的本地化接口
NSLocalizedStringFromTable(key, tbl, comment)

导航条、列表、工具栏、输入框是 Global IM UIKit 中最中心的几大 Component。

导航条

Header 组件是对体系导航条 UINavigationBar 的自界说,经过界说左右按钮和标题,在 ViewController 加载时赋值给 UINavigationBar。

@interface RCBaseHeaderView : NSObject
/// 左侧按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *leftItems;
/// 右侧按钮,默认修正按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *rightItems;
/// 标题 baseView
@property (nonatomic, strong) RCBarTitleView *titleView;
@end

按钮支撑标题、图片和自界说视图显现,点击事情经过 Action 抛出。

@interface RCBarItem : NSObject
/// 称号
@property (nonatomic, copy, nullable) NSString *title;
/// 图标
@property (nonatomic, strong, nullable) UIImage *image;
/// 事情回调,假如自界说视图,这里不会有事情回调
@property (nonatomic, copy, nullable) RCBarItemAction action;
/// 自界说视图
/// 自界说视图的点击事情也需求自界说,当点击时,不会触发 action 调用
@property (nonatomic, strong, nullable) UIView *customView;
/// 标签
@property (nonatomic, assign) RCBarItemTag tag;
@end

标题是自界说视图,支撑标题和副标题两个 Label。

@interface RCBarTitleView : UIView
/// 标题 label
@property (nonatomic, strong) UILabel *titleLabel;
/// 状况 label
@property (nonatomic, strong) UILabel *statusLabel;
@end

列表

在 List 组件中,运用 UITableView 完成列表展现,支撑设置占位视图:

@interface RCBaseListView : UIView
// 列表
@property (nonatomic, strong) UITableView *tableView;
// 占位页面
@property (nonatomic, strong) UIView *emptyView;
@end

无论会话还是音讯,都需求分页加载,UITableView 的 insert 办法十分契合会话列表的动态加载作用,能够流通的刺进下页数据。

在会话列表中,每个会话对应一个 Cell,会话的展现款式根本共同,在列表中运用 RCChatCell 展现。开发者能够注册自界说 Cell:

@interface RCChatListView : RCBaseListView
...
/// 将会话 Cell 替换为承继 RCChatCell 的自界说 Cell
- (void)replaceChatCellWithClass:(Class)cellClass;
@end

在会话页面中,不同音讯的展现款式区别很大,每个音讯 Cell 都会承继于 RCMessageCell 自界说款式。同样,开发者也能够注册自界说 Cell:

@interface RCMessageListView : RCBaseListView
...
/// 注册自界说音讯 Cell
/// - Parameters:
///   - cellClass: 自界说 Cell,需求承继 RCBaseMessageCell
///   - messageClass: 音讯体,支撑自界说音讯和内置音讯
- (void)registerClass:(Class)cellClass
      forMessageClass:(Class)messageClass;
@end

工具栏

工具栏通常用于多选操作,依据事务需求,设置修正按钮,运用工具栏需求考虑体系的 tabBar。

@interface RCTabBar : UIView
/// 按钮容器,用于布置按钮
@property (nonatomic, strong) UIStackView *containerHStackView;
/// 顶部线条
@property (nonatomic, strong) UIView *topLineView;
/// 按钮
@property (nonatomic, copy) NSArray<RCBarItem *> *items;
@end

在会话列表中,修正栏固定为底部显现,会向上推起会话列表。假如有体系 tabBar,就会将 tabBar 隐藏,结束挑选后,康复 tabBar。在会话页面中,工具栏会直接掩盖输入框。

输入框

Input 是一个功用聚合度比较高的组件,包括了文本输入、多媒体挑选、录音、引证、贴纸等子组件。

@interface RCInputBar : UIView
/// 引证 view
@property (nonatomic, readonly) RCReferenceView *referenceView;
/// 输入框
@property (nonatomic, readonly) RCInputTextView *textView;
/// 贴纸 view
@property (nonatomic, readonly) RCStickerBoardView *stickerBoardView;
/// 输入框左面 items,默以为 addItem
@property (nonatomic, copy) NSArray<RCBarItem *> *leftItems;
/// 输入框右边 items,默以为 recordItem,当输入文本时,变为 sendItem
@property (nonatomic, copy) NSArray<RCBarItem *> *rightItems;
/// 加号按钮扩展 items,默以为:相册、相机、文件
/// 在每次扩展面板展现前设置收效
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *addExpandItems;
@end

组件定制化

为了与运用中的交际模块平滑衔接,Global IM UIKit 的 UI 组件需求支撑灵敏定制和高重用。开发者能够经过承继、布局和装备的二级重写和三级自界说办法,定制符合事务场景和体会的功用。

承继

引荐开发者运用承继的办法跳转会话列表和会话页面,在自界说的 ViewController 中,开发者能够经过重写父办法,完成自界说操作:

@interface TestChatViewController : RCChatViewController
@end
@implementation TestChatViewController
- (void)reloadHeaderView {
    [super reloadHeaderView];
    // TODO custom
}
@end

也能够承继组件来完成自界说事务逻辑,在页面展现时经过 setter 的办法,设置为自界说的组件:

@interface TestChatHeaderView : RCChatHeaderView
@end
@implementation TestChatHeaderView
- (void)configure:(RCChatModel *)model {
    [super configure:model];
}
@end
TestChatViewController *controller = [[TestChatViewController alloc] initWithChatModel:model];
controller.headerView = [[TestChatHeaderView alloc] init];

重写事情署理回调,也能够增加自界说操作,如列表的署理事情:

/// 列表事情署理
@protocol RCMessageListViewDelegate <NSObject>
...
/// 列表中的 Cell 即将加载
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 音讯目标
- (void)listView:(RCMessageListView *)listView willLoadCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;
/// 列表中的 Cell 加载完成
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 音讯目标
- (void)listView:(RCMessageListView *)listView didLoadCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;
/// 列表中的 Cell 即将展现
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 音讯目标
- (void)listView:(RCMessageListView *)listView willDisplayCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;
/// 更多回调接口,请参阅 SDK 接口
...
@end

布局

在各组件中,会运用 StackView 容器承载 View,能够在 StackView 中直接增加更多的 View。

这种办法首要用在列表的 Cell 中,Cell 运用 Estimate 高度,充沛运用 iOS 的 Auto Layout 功用特性,Cell 实际高度会由内容主动撑开。

@interface RCChatCell : RCTableViewCell
/// 会话容器,头像、会话内容
@property (nonatomic, readonly) UIStackView *containerStackView;
...
/// 会话内容,在 containerStackView 中
@property (nonatomic, readonly) UIStackView *contentStackView;
/// 会话上部分内容,在 containerStackView 中,用户信息、时刻
@property (nonatomic, readonly) UIStackView *contentTopStackView;
...
/// 会话下部分内容,在 containerStackView 中,音讯预览、已读
@property (nonatomic, readonly) UIStackView *contentBotStackView;
...
@end

开发者需求熟悉 StackView 的根底运用办法,留意 StackView 的布局方向。

装备

经过装备的办法增修正现有的功用逻辑,首要适用依据 RCBarItem 的按钮事情,例如:导航条左右按钮是可变数组,能够直接修正该数组,并改写 UI 即可:

@implementation TestChatViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    RCBarItem *item = [RCBarItem itemWithTitle:nil image:CustomImage action:^(RCBarItem *item) {
        // TODO custom
    }];
    [self.headerView.leftItems addObject:item];
    [self reloadHeaderView];
}
@end

会话和音讯列表的展现款式是需求最多的自界说组件,开发者能够经过 List 供给的注册办法,完全自界说 Cell 的展现款式。

支撑 Module 和 Component 的二级重写之外,如文章开头所述,融云 Global IM UIKit 亦对某些通用功用支撑一键切换修正。

在支撑开发者全球化事务的多样实践中,融云发现有些通用功用也需求跟随事务完成多样化。例如,单群聊自己的头像和昵称是否展现等。

针对这些功用,开发者经过重写组件来完成多样化的本钱比较高,融云 Global IM UIKit 直接将此类功用直接封装成开关(与多言语、主题切换相似),开发者可依据事务场景一键切换运用。


数据处理

除了用户能够直观看到的 UI 组件外,数据流处理也是 Global IM UIKit 中的重要环节。

数据处理的首要难点在于:需求将杂乱的事务状况计算放入串行行列中异步处理。经过节省优化,批量处理音讯接纳或同步已读和回执。

首要的数据处理有音讯接纳、音讯加载、未读数、音讯已读回执、音讯已读同步等,需求考虑音讯的时序、IO 拜访的频率、音讯属性的缓存、UI 和数据逻辑的交互。

数据行列

音讯的接纳、发送、加载和操作等都是并发事情,为了能够将这些事情有序、安全地传递给 UI 组件显现,需求有一个一致可调度的串行行列,确保数据和 UI 的之间的彼此映射。

在 ViewModel 中,有专门处理数据的串行行列 dataQueue,一切事情都会放入到串行行列中处理,ViewModel 会将处理结果经过回调接口,分发给对应组件改写。

/// ViewModel 根底类
@interface RCBaseViewModel : NSObject
/// 操作数据的线程,一切对数据的操作有必要在该线程中处理
@property (nonatomic, readonly) dispatch_queue_t dataQueue;
#pragma mark - Perform -
- (void)asyncPerformBlock:(dispatch_block_t)block;
- (void)syncPerformBlock:(dispatch_block_t)block;
@end
/// 会话数据回调接口
@protocol RCChatViewModelDelegate <NSObject>
...
/// 列表数据改写
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 音讯目标数组
- (void)viewModel:(RCChatViewModel *)viewModel
didReloadMessages:(NSArray<RCMessageModel *> *)messages;
/// 刺进列表数据
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 音讯目标数组
///   - indexSet: 音讯目标数组对应的方位
- (void)viewModel:(RCChatViewModel *)viewModel
didInsertMessages:(NSArray<RCMessageModel *> *)messages
       atIndexSet:(NSIndexSet *)indexSet;
/// 列表数据更新
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 音讯目标数组
///   - indexSet: 音讯目标数组对应的方位
- (void)viewModel:(RCChatViewModel *)viewModel
didUpdateMessages:(NSArray<RCMessageModel *> *)messages
       atIndexSet:(NSIndexSet *)indexSet;
/// 更多回调接口,请参阅 SDK 接口
...
@end

数据加载

在列表中,为了流通地滚动预加载体会,ViewModel 中除了 reload 办法外,也供给了加载上一页和下一页的办法。

随列表滚动,当到达加载下一页的条件时,会调用 loadNextPage 办法加载下一页数据。ViewModel 会经过署理回调的办法,将下一页的数据传递给 List 显现出来。

@interface RCChatListViewModel : RCBaseViewModel
...
/// 加载会话数据
- (void)reloadData;
/// 拉取下一页数据
- (void)loadNextPage;
...
@end
@protocol RCChatListViewModelDelegate <NSObject>
/// 改写会话列表
/// - Parameter viewModel: ViewModel
/// - Parameter chatModels: 会话数据
- (void)viewModel:(RCChatListViewModel *)viewModel chatListDidReload:(NSArray<RCChatModel *> *)chatModels;
/// 刺进会话
/// - Parameter viewModel: ViewModel
/// - Parameter indexSet: 会话 index list
- (void)viewModel:(RCChatListViewModel *)viewModel chatListDidInsertAtIndexSet:(NSIndexSet *)indexSet;
...
@end

数据节省

尽管数据线程能够确保音讯有序展现到 UI 中,但在接纳音讯时,需求考虑音讯风暴的情况。即,接纳很多音讯直接改写 UI,会导致 UI 卡顿,甚至崩溃。

因而,需求增加节省操作,在延时一定时刻后,进行批量处理。

- (void)onReceived:(RCMessage *)message left:(int)nLeft object:(id)object {
    self.cachedMessages[@(message.messageId)] = message;
    if (nLeft == 0) {
        __weak typeof(self) weakSelf = self;
        [self.throttle scheduleThrottleWithBlock:^{
            [weakSelf rc_throttleUpdateChatList];
        }];
    }
}

在音讯接纳和发送时,还会触发音讯已读回执、未读数整理、已读多端同步。已读回执和已读同步在接口调用时,需求经过节省汇总,在一定时刻后一致上报。

__weak typeof(self) weakSelf = self;
[self.readReceiptThrottle scheduleThrottleWithBlock:^{
    [weakSelf rc_sendReadReceipt];
}];
__weak typeof(self) weakSelf = self;
[self.readSyncThrottle scheduleThrottleWithBlock:^{
    [weakSelf rc_syncReadTimeThrottle];
}];

音讯处理

会话支撑置顶、免打扰、符号未读和删去,当 ViewModel 中调用接口后,会触发远端和数据库状况更新,加载到内存的数据也需求维护更新。UI 的改写直接依赖于内存数据,内存数据需求确保和本地数据库共同,本地数据库频繁触发 IO 拜访。

能够将 ViewModel 中部分数据操作分配到内存中,一起内存依据处理结果和 UI 合作改写展现。例如:置顶操作,调用设置接口后,在内存中更新状况和重新排序,并将结果反馈给 List 进行 UI 改写和动画。

@interface RCChatListViewModel : RCBaseViewModel
...
/// 移除数据,依据 models 批量移除对应会话,一起删去会话中的音讯
/// 数据增加完成后,会触发 viewModel:chatListDidRemoveAtIndexSet: 回调
- (void)deleteChatModels:(NSArray<RCChatModel *> *)models;
/// 符号已读未读
/// 符号完成后,会触发 viewModel:chatListDidUpdateAtIndexSet: 回调
/// - Parameter model: 会话目标
- (void)toggleRead:(RCChatModel *)model;
...
/// 设置/撤销置顶
- (void)toggleTop:(RCChatModel *)model;
...
/// 设置/撤销免打扰
- (void)toggleNotification:(RCChatModel *)model;
@end

接入指南

Global IM UIKit SDK 的中心集中于即时通讯的事务功用界面的完成,依据融云 RongCloudIM 供给的高并发、高可用通讯才能,以习惯海外用户运用习惯的交互规划完成封装。开发者选用 Global IM UIKit SDK,在 快速上手文档支撑下,“分钟级”接入即可完成单群聊完好功用。

集成准备

创立 融云开发者账号,获取App Key

开始之前,需创立融云开发者账号并获取 App Key。在开发者后台,体系会主动为新账号创立一个运用。默认运用国内数据中心,并供给开发环境。假如您现已有融云开发者账号,能够直接创立新运用。

集成 SDK

引荐运用 Pod 集成,如需手动集成,请移步官网下载 Framework,将 Framework 直接导入 App 即可。

☑ 在 podfile 中增加如下内容:

pod 'RongCloudGlobal/IMUIKit'

☑ 请在终端中运转以下命令:

pod install

假如呈现找不到相关版别的问题,可先履行 pod repo update,再履行 pod install。

☑ 上一步完成后,CocoaPods 会在您的工程根目录下生成一个 xcworkspace 文件,只需经过 XCode 翻开该文件即可加载工程。

完成聊天功用

初始化

Global IM UIKit SDK 依赖于 IMLibCore 的即时通讯才能,运用前需求对 IMLibCore 进行初始化。IMLibCore 中心类为 RCCoreClient,初始化时,需求传入生产或开发环境的 App Key。

#import <RongIMLibCore/RongIMLibCore.h>
NSString *appKey = @"Your_AppKey"; // example: bos9p5rlcm2ba
RCInitOption *initOption = nil;
[[RCCoreClient sharedCoreClient] initWithAppKey:appKey option:initOption];

☑ 衔接

用户 Token 是与用户 ID 对应的身份验证令牌,是运用程序的用户在融云的唯一身份标识。运用客户端在运用融云即时通讯功用前有必要与融云建立 IM 衔接,衔接时有必要传入 Token。

用户能够在开发者后台快速创立一个用户 John Doe,创立后返回 Token,运用该 Token 衔接。

    [[RCCoreClient sharedCoreClient] connectWithToken:@"John Doe token" dbOpened:^(RCDBErrorCode code) {
        //音讯数据库翻开,能够进入到主页面
    } success:^(NSString *userId) {
        //衔接成功
    } error:^(RCConnectErrorCode errorCode) {
        if (status == RC_CONN_TOKEN_INCORRECT) {
            //从 APP 服务获取新 token,并重连
        } else {
            //无法衔接到 IM 服务器,请依据相应的错误码作出对应处理
        }
    }];

☑ 用户信息

要在 Global IM UIKit 上展现用户、群组的头像、称号等,需求运用层(App)主动向 IMKit SDK 供给用户信息。为了完全体会 Global IM UIKit 的 UI,我们将直接在用户信息数据库中写入对运用户 ID 的头像、称号信息。

下面设置了本地登录用户 John Doe 和另一个用户 Jane Smith 的头像、昵称。

[RCIMKitClient shared].enableUserInfoPersistence = YES;
RCUserInfo *currentUserJohnDoe = [[RCUserInfo alloc] initWithUserId:@"userIdJohnDoe" name:@"John Doe" portrait:userPortraitUri];
[RCIMKitClient shared].currentUserInfo = currentUserJohnDoe;
RCUserInfo *JaneSmith = [[RCUserInfo alloc] initWithUserId:@"userIdJaneSmith" name:@"Jane Smith" portrait:@"http://portrait"];
[[RCIMKitClient shared] refreshUserInfoCache:JaneSmith];

☑ 会话列表

Global IM UIKit 已默认供给会话列表页面和会话页面。会话列表页面展现当时用户参加的一切单聊、群聊、体系会话,在会话页面可进行音讯修正、检查、回复、发送等活动。

衔接成功后即可跳转到默认会话列表界面。引荐承继 SDK 中的 RCChatListViewController,例如 TestChatListViewController 类,示例如下:

@interface TestChatListViewController : RCChatListViewController
@end

初始化自界说的会话列表页面 TestChatListViewController,会话列表支撑显现单聊、群聊、体系会话。会话列表详细运用办法,参见**会话列表页面**。

TestChatListViewController *listVC = [[TestChatListViewController alloc] init];
[self.navigationController pushViewController:listVC animated:YES];

☑ 会话

为了快速体会,能够从开发者后台【北极星】开发者工具箱【IM Server API 调试】页面模仿用户 Jane Smith 向当时登录的用户 John 发送一条文本音讯。

只要供给对方的 userId,融云就可支撑跟对方建议聊天。运用客户端能够经过用户 ID、群聊会话 ID 接纳音讯。

以上就是 Global IM UIKit SDK 的全部集成过程,“分钟级”接入即可感触最具灵敏性的 IM UI 技能架构,以及与海外用户运用体会对齐的交互规划。