自定义消息
协作中台 SDK 提供了丰富的消息类型(见 消息类型),当这些消息类型无法满足业务需求时,可接入新的消息类型。如下图的天气消息。

快速上手
步骤1:处理自定义消息的发送
调用 消息发送接口 发送自定义消息。举例:
let appCustomizeMsg = KIMAppCustomizeMsg(content: "your_content", customizeType: "your_customize_type", msgDesc: "your_message_summary")
KIMCore.shared.sendMessage(chatId: "12312412", appCustomizeMsg: appCustomizeMsg)KIMAppCustomizeMsg *appCustomizeMsg = [[KIMAppCustomizeMsg alloc] initWithContent:@"your_content" customizeType:@"your_customize_type" msgDesc:@"your_message_summary"];
[KIMCore.shared sendMessageWithChatId:@"12312412" appCustomizeMsg:appCustomizeMsg notices:nil extra:nil replyMsgId:nil msgConfig:nil pushConfig:nil];步骤2:自定义消息 Cell(自定义消息的解析与样式处理)
处理自定义消息的解析
a. 实现KIMChatMessageDataSourceBuilder协议:将KIMMessage的appCustomizeMsg字段内容,按需进行转换,并存储到KIMChatMessage的customData字段中
b. 实现KIMChatMessageDataSourceBuilder协议:将KIMChatMessage的customData字段内容,按需进行转换,并存储到KIMMessageCellState的messageContent.customContent字段中
原理请参考 数据结构与转换流程。
自定义消息 Cell
每一种自定义消息,都需要定义消息气泡的显示 Cell:
class YourCustomCell: KIMMessageBaseCell {
override func setUp() {
super.setUp()
// 构建 cell 视图
}
override func configureCell(model: KIMMessageCellViewModel, tableView: UITableView) {
super.configureCell(model: model, tableView: tableView)
// 根据 model.cellState.messageContent 的 customContent 字段,配置 cell
guard let content = model.cellState.messageContent.customContent as? yourCustomContent else {
return
}
// 配置 cell 数据
yourLabel.text = content.xxx
}
}步骤3:自定义消息摘要
系统默认使用自定义消息的msgDesc字段作为会话列表消息摘要。如果需要对摘要进行自定义,可以重写 manager 的getSummaryBodyName和getSummaryBodyContent方法:

会话列表的消息摘要由两个部分构成:
name:消息发布者名称,可通过重写getSummaryBodyName来自定义content:消息摘要,可通过重写getSummaryBodyContent来自定义
示例如下:
class YourMessageManager: KIMCustomChatMessageManager<YourMessageCell, YourAsyncTask, YourCacheType> {
/// 自定义消息摘要名称
override func getSummaryBodyName(appCustomizeMsg: KIMAppCustomizeMsg, msgId: String) -> String {
return "your custom summary body name"
}
/// 自定义消息摘要正文
override func getSummaryBodyContent(appCustomizeMsg: KIMAppCustomizeMsg, msgId: String) -> String {
return "your custom summary body content"
}
}步骤4:定义并注册自定义消息管理器
自定义消息框架提供了一个管理器KIMCustomChatMessageManager,用于封装自定义消息的各类接口,定义如下:
class KIMCustomChatMessageManager<
C: KIMMessageBaseCell,
T: KIMAsyncTask,
CacheType
> {
}泛型类型说明:
| 泛型 | 用途 | 说明 |
|---|---|---|
| C | 自定义消息 Cell 类 | 需继承自KIMMessageBaseCell; |
| T | 自定义消息关联的异步任务类 | 需继承自KIMAsyncTask,如果此自定义消息不涉及异步任务 (如 HTTP 请求),此处直接填KIMAsyncTask即可;具体见 异步任务管理 |
| CacheType | 自定义消息缓存的数据结构 | 如果此自定义消息不需要使用缓存,此处直接填Any即可;具体见 缓存管理 |
每接入一种自定义消息,都需要提供一个继承自KIMCustomChatMessageManager的自定义 manager 子类:
class YourMessageManager: KIMCustomChatMessageManager<YourMessageCell, YourAsyncTask, YourCacheType> {
}
class YourMessageCell: KIMMessageBaseCell {
}
class YourAsyncTask: KIMAsyncTask {
}
class YourCacheType {
}子类化 manager 后,需要在 App 启动时初始化 manager 并注册。KIMCustomChatMessageManager初始化函数如下:
public init(
customizeType: String,
dataSourceBuilderBlock: @escaping (KIMServicePlugin.BodyType) -> KIMChatMessageDataSourceBuilder,
cellStateBuilderBlock: @escaping (KIMServicePlugin.BodyType) -> KIMChatMessageCellStateBuilder,
operationMenuServiceBlock: ((KIMServicePlugin.BodyType) -> KIMChatMessageOperationMenuService)? = nil,
quickReplyMenuServiceBlock: ((KIMServicePlugin.BodyType) -> KIMQuickReplyMenuService)? = nil
)参数说明:
| 参数名 | 类型 | 说明 |
|---|---|---|
| customizeType | String | 此自定义消息的唯一类型标识 |
| dataSourceBuilderBlock | (KIMServicePlugin.BodyType) -> KIMChatMessageDataSourceBuilder | 在 block 中创建并返回KIMChatMessageDataSourceBuilder,用于将业务 ModelKIMMessage转换为 ViewModelKIMChatMessage;具体见 数据结构与转换流程、自定义 DataSourceBuilder |
| cellStateBuilderBlock | (KIMServicePlugin.BodyType) -> KIMChatMessageCellStateBuilder | 在 block 中创建并返回KIMChatMessageCellStateBuilder,用于将 ViewModelKIMChatMessage转换为 CellModelKIMMessageBuildInCellModel,具体见 数据结构与转换流程、自定义 CellStateBuilder |
| operationMenuServiceBlock | (KIMServicePlugin.BodyType) -> KIMChatMessageOperationMenuService | 在 block 中创建并返回KIMChatMessageOperationMenuService,用于 自定义消息长按菜单 |
| quickReplyMenuServiceBlock | (KIMServicePlugin.BodyType) -> KIMQuickReplyMenuService | 在 block 中创建并返回KIMQuickReplyMenuService,用于 自定义快捷回复 |
完成 manager 初始化后,调用register实例方法进行注册。注意:需在 App 启动的时候调用;仅调用一次即可。下面的例子在 App 启动的application(_:didFinishLaunchingWithOptions:)方法中调用:
@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let manager = YourMessageManager(
customizeType: "unique_message_customize_type",
dataSourceBuilderBlock: { _ in YourChatMessageDataSourceBuilder() },
cellStateBuilderBlock: { _ in YourChatMessageCellStateBuilder() },
operationMenuServiceBlock: { _ in YourChatMessageOperationMenuService() }
)
manager.register()
}
}调用register方法完成 manager 注册后,可以通过default类属性,来获取注册的 manager 实例:
YourMessageManager.default进而调用 manager 提供的各种方法:
YourMessageManager.default.someMethod()注:manager 的定义涉及到了泛型,而 Objective-C 不支持泛型,因此 manager 的定义只能使用 Swift 来完成。关于如何在 Objective-C 代码中注册 Swift 定义的 manager,请见 Objective-C 接入方案。
至此,我们便完成了接入自定义消息的所有必要步骤。
可选流程:
如果自定义消息涉及到异步请求,参考 异步任务管理
如果需要在内存中缓存数据,参考 缓存管理
如果自定义消息需要支持长按菜单,参考 自定义消息长按菜单
如果需要调整快捷回复的支持,参考 支持快捷回复
如果需要在消息渲染的特定阶段添加逻辑,参考 生命周期管理
如果需要在 Objective-C 中使用自定义消息的能力,参考 Objective-C 接入方案
下方的文档为详细的 API 说明,可在接入的过程中按需查看。
API 说明
发送自定义消息
调用举例:
let msgContent = [
city: "广州",
longitude: "113.280637",
latitude: "23.125178"
]
guard let jsonData = try? JSONEncoder().encode(msgContent),
let jsonString = String(data: jsonData, encoding: .utf8) else {
return
}
let customizeMsg = KIMAppCustomizeMsg(content: jsonString, customizeType: "check_weather", msgDesc: "查询广州未来一周天气")
KIMCore.shared.sendMessage(chatId: "908753402", appCustomizeMsg: customizeMsg)NSDictionary *msgContent = @{
@"city": @"广州",
@"longitude": @"113.280637",
@"latitude": @"23.125178"
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:msgContent options:0 error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
if (error) {
return;
}
KIMAppCustomizeMsg *customizeMsg = [[KIMAppCustomizeMsg alloc] initWithContent:jsonString customizeType:@"check_weather" msgDesc:@"查询广州未来一周天气"];
[[KIMCore shared] sendMessageWithChatId:@"908753402" appCustomizeMsg:customizeMsg notices:nil extra:nil replyMsgId:nil msgConfig:nil pushConfig:nil];自定义消息长按菜单

步骤如下:
自定义类,实现
KIMChatMessageOperationMenuService协议在协议方法
makeOperationMenuItems中:仅处理当前注册的自定义消息
初始化
KIMMessageMenuItem,并设置action属性来实现菜单项的点击处理系统默认提供 5 种内置菜单项:回复、撤回、转发、多选、收藏;如果需要集成这些内置菜单项,需调用
KIMChatUI.shared.servicePlugin.getDefaultService获取系统默认的KIMChatMessageOperationMenuService实现,并调用其makeOperationMenuItems生成内置菜单项最终,以 array 的形式返回所有的
KIMMessageMenuItem
在manager 初始化时传入
示例如下:
class YourChatMessageOperationMenuService: KIMChatMessageOperationMenuService {
let bodyType: KIMServicePlugin.BodyType
weak var delegate: KIMChatMessageOperationMenuServiceDelegate?
/// 获取系统默认的 KIMChatMessageOperationMenuService 实现
private lazy var def: KIMChatMessageOperationMenuService? = {
let service = KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageOperationMenuService.self,
bodyType: bodyType
)
/// 这一步不能少
service?.delegate = delegate
return service
}()
init(bodyType: KIMServicePlugin.BodyType) {
self.bodyType = bodyType
}
func makeOperationMenuItems(chatMessage: KIMMessageCellViewModel, view: UIView?, controller: KIMChatMessageViewController) -> [KIMMessageMenuItem]? {
/// 仅对当前自定义消息类型生效,其他类型均返回 nil
guard YourMessageManager.default.shouldProcessChatMessage(chatMessage) else {
return nil
}
/// 生成系统内置的菜单项
let systemItems = def?.makeOperationMenuItems(chatMessage: chatMessage, textView: textView, controller: controller) ?? []
/// 自定义菜单项
let customItem = KIMOperationMenuItem(icon: UIImage(named: "kim_chat_add"), title: "自定义1", identifier: "custom_op_1")
customItem.action = { [weak self] in
// 点击处理逻辑
return true
}
return systemItems + [customItem]
}
}//
// YourChatMessageOperationMenuService.h
//
#import <Foundation/Foundation.h>
@import KIMKit;
@interface YourChatMessageOperationMenuService: NSObject<KIMChatMessageOperationMenuService>
@property (nonatomic, weak) id<KIMChatMessageOperationMenuServiceDelegate> delegate;
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType;
@end//
// YourChatMessageOperationMenuService.m
//
#import "YourChatMessageOperationMenuService.h"
@import KIMKit;
@import KIMCore;
@interface YourChatMessageOperationMenuService ()
@property (strong, nonatomic) id<KIMChatMessageOperationMenuService> def;
@property (nonatomic, assign) enum KIMServiceBodyType bodyType;
@end
@implementation YourChatMessageOperationMenuService
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType {
self = [super init];
if (self) {
_bodyType = bodyType;
}
return self;
}
- (NSArray<KIMOperationMenuItem *> * _Nullable)makeOperationMenuItemsWithChatMessage:(KIMChatMessage * _Nonnull)chatMessage textView:(UITextView * _Nullable)textView controller:(UIViewController * _Nullable)controller {
// 仅对当前自定义消息类型生效,其他类型均返回 nil
if (![chatMessage.coreMessage.type isEqualToString:@"kim-app-customize"] || ![chatMessage.coreMessage.appCustomizeMsg.customizeType isEqualToString:@"weather_2"]) {
return nil;
}
// 生成系统内置的菜单项
NSArray<KIMOperationMenuItem *> *items = [self.def makeOperationMenuItemsWithChatMessage:chatMessage textView:textView controller:controller] ?: @[];
// 自定义菜单项
KIMOperationMenuItem *weatherCustom1 = [[KIMOperationMenuItem alloc] initWithIcon:[UIImage imageNamed:@"kim_chat_add"] title:@"自定义1" identifier:[[KIMOperationMenuIdentifier alloc] initWithRawValue:@"custom_weather_op_1"]];
weatherCustom1.action = ^{
// 点击处理逻辑
return YES;
};
return [items arrayByAddingObjectsFromArray:@[weatherCustom1]];
}
// 获取系统默认的 KIMChatMessageOperationMenuService 实现
- (id<KIMChatMessageOperationMenuService>)def {
if (_def == nil) {
_def = [ObjcBridge getDefaultChatMessageOperationMenuServiceWithBodyType:self.bodyType];
// 这一步不能少
_def.delegate = _delegate;
}
return _def;
}
@end注:由于KIMChatUI.shared.servicePlugin.getDefaultService的定义包含泛型,因此无法直接在 Objective-C 代码中调用。这里使用 Swift 类做了一个桥接:
@objcMembers
class ObjcBridge: NSObject {
class func getDefaultChatMessageOperationMenuService(bodyType: KIMServicePlugin.BodyType) -> KIMChatMessageOperationMenuService? {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageOperationMenuService.self,
bodyType: bodyType
)
}
}自定义收藏页菜单
消息收藏列表页、消息收藏详情页,均支持自定义消息的渲染。
在消息收藏列表页,长按自定义消息,会弹出操作菜单:
在自定义消息的收藏详情页,点击右上角 ... 按钮,也会弹出操作菜单:

上述页面中的“自定义 1”即为自定义操作项,自定义步骤如下:
自定义类,实现
KIMFavoriteChatMessageActionService协议实现协议方法
makeFavoriteActionItems:判断是否为应该处理的自定义消息
初始化
KIMFavoriteActionItem,并设置action属性来实现菜单项的点击处理最终,以 array 的形式返回所有的
KIMFavoriteActionItem
在manager 初始化时传入
示例如下:
class YourFavoriteChatMessageActionService: KIMFavoriteChatMessageActionService {
func makeFavoriteActionItems(message: KIMMessage, controller: UIViewController) -> [KIMFavoriteActionItem]? {
/// 仅对当前自定义消息类型生效,其他类型均返回空数组
guard YourMessageManager.default.shouldProcessMessage(message) else {
return nil
}
/// 自定义操作项
let item1 = KIMFavoriteActionItem(title: "自定义1", identifier: "custom_op_1")
item1.action = { [weak self] in
/// 点击处理逻辑
return true
}
return [item1]
}
}消息快捷回复菜单

SDK 已对自定义消息提供了默认的快捷回复实现,无需编写代码,即可支持上图中的快捷回复控件。
如果需要对快捷回复项进行定制,或者希望隐藏快捷回复,则需要进行自定义,步骤如下:
自定义类,实现
KIMQuickReplyMenuService协议在协议方法
makeQuickReplyMenuItems中:判断是否为应该处理的自定义消息
初始化
KIMQuickReplyItem,并设置action属性来实现菜单项的点击处理系统默认提供 6 项 Emoji 快捷回复项,如果需要集成这些内置菜单项,需调用
KIMChatUI.shared.servicePlugin.getDefaultService获取系统默认的KIMQuickReplyMenuService实现,并调用其makeQuickReplyMenuItems生成内置菜单项最终,以 array 的形式返回所有的
KIMQuickReplyItem(最多返回 6 项)
在manager 初始化时传入
示例如下:
class YourQuickReplyMenuService: KIMQuickReplyMenuService {
let bodyType: KIMServicePlugin.BodyType
var delegate: KIMKit.KIMQuickReplyMenuServiceDelegate?
private lazy var def: KIMQuickReplyMenuService? = {
let service = KIMChatUI.shared.servicePlugin.getDefaultService(
KIMQuickReplyMenuService.self,
bodyType: bodyType
)
/// 这一步不能少
service?.delegate = delegate
return service
}()
init(bodyType: KIMServicePlugin.BodyType) {
self.bodyType = bodyType
}
func makeQuickReplyMenuItems(chatMessage: KIMKit.KIMChatMessage) -> [KIMKit.KIMQuickReplyItem]? {
/// 仅对当前自定义消息类型生效,其他类型均返回 nil
/// 这一步不能少
guard YourMessageManager.default.shouldProcessChatMessage(chatMessage) else {
return nil
}
/// 生成系统内置的菜单项
let systemItems = def?.makeQuickReplyMenuItems(chatMessage: chatMessage) ?? []
/// 自定义菜单项
/// 不可用的 id:0、-1000、 1000100 ~ 1013500
let customItem = KIMQuickReplyItem(id: 1, name: "custom_quick_reply_item_1", image: UIImage(named: "your_image"))
customItem.action = {
// 点击处理逻辑
return true
}
// 取前 6 项
return Array((systemItems + [customItem]).prefix(6))
}
func makeQuickReplyDataSource(chatMessage: KIMKit.KIMChatMessage) -> [KIMKit.KIMQuickReplyItem]? {
nil
}
}//
// YourQuickReplyMenuServiceObjc.h
//
#import <Foundation/Foundation.h>
@import KIMKit;
@interface YourQuickReplyMenuServiceObjc: NSObject<KIMQuickReplyMenuService>
@property (nonatomic, weak) id<KIMQuickReplyMenuServiceDelegate> delegate;
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType;
@end//
// YourQuickReplyMenuServiceObjc.m
//
#import "YourQuickReplyMenuServiceObjc.h"
@import KIMKit;
@import KIMCore;
@interface YourQuickReplyMenuServiceObjc ()
@property (strong, nonatomic) id<KIMQuickReplyMenuService> def;
@property (nonatomic, assign) enum KIMServiceBodyType bodyType;
@end
@implementation YourQuickReplyMenuServiceObjc
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType {
self = [super init];
if (self) {
_bodyType = bodyType;
}
return self;
}
- (NSArray<KIMQuickReplyItem *> * _Nullable)makeQuickReplyMenuItemsWithChatMessage:(KIMChatMessage * _Nonnull)chatMessage {
// 仅对当前自定义消息类型生效,其他类型均返回 nil
if (![chatMessage.coreMessage.type isEqualToString:@"kim-app-customize"] || ![chatMessage.coreMessage.appCustomizeMsg.customizeType isEqualToString:@"weather_2"]) {
return nil;
}
// 获取系统默认的菜单项
NSArray<KIMQuickReplyItem *> *systemItems = [self.def makeQuickReplyMenuItemsWithChatMessage:chatMessage] ?: @[];
// 自定义菜单项
// 不可用的 id:0、-1000、 1000100 ~ 1013500
KIMQuickReplyItem *customItem = [[KIMQuickReplyItem alloc] initWithId:1 name:@"custom_quick_reply_item_1" image:[UIImage imageNamed:@"kim_chat_add"]];
customItem.action = ^{
// 点击处理逻辑
return YES;
};
NSArray<KIMQuickReplyItem *> *allItems = [@[customItem] arrayByAddingObjectsFromArray:[systemItems arrayByAddingObject:customItem]];
NSArray<KIMQuickReplyItem *> *limitedItems = [allItems subarrayWithRange:NSMakeRange(0, MIN(6, allItems.count))];
return limitedItems;
}
- (NSArray<KIMQuickReplyItem *> * _Nullable)makeQuickReplyDataSourceWithChatMessage:(KIMChatMessage * _Nonnull)chatMessage {
return @[];
}
- (id<KIMQuickReplyMenuService>)def {
if (_def == nil) {
_def = [ObjcBridge getDefaultQuickReplyMenuServiceWithBodyType:self.bodyType];
_def.delegate = _delegate;
}
return _def;
}
@end注:由于KIMChatUI.shared.servicePlugin.getDefaultService的定义包含泛型,因此无法直接在 Objective-C 代码中调用。这里使用 Swift 类做了一个桥接:
@objcMembers
class ObjcBridge: NSObject {
class func getDefaultQuickReplyMenuService(bodyType: KIMServicePlugin.BodyType) -> KIMQuickReplyMenuService? {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMQuickReplyMenuService.self,
bodyType: bodyType
)
}
}消息气泡生命周期
为提供精细化的消息管理能力,协作中台 SDK 针对自定义消息,定义了 6 个生命周期:

| 生命周期 | 说明 | 使用建议 |
|---|---|---|
| onFetchData | 数据预请求 | 发起异步请求 |
| onBindData | 预请求完毕 | 更新消息相关数据结构体,刷新 cell |
| onCreateView | 准备消息 View | 创建自定义消息 cell,并完成 cell 配置 |
| onAppear | 消息即将显示 | 按需 |
| onDisappear | 消息结束显示 | 资源清理 |
| onRecycled | 取消数据预请求 | 取消异步请求 |
注意:以上生命周期,SDK 均提供默认的标准实现,在不做任何重载实现的情况下,框架可提供默认功能。
重载案例如下:
class YourMessageManager: KIMCustomChatMessageManager<YourMessageCell, YourAsyncTask, YourCacheType> {
override func onRecycled(messages: [KIMChatMessage], viewModel: KIMChatMessageViewModel) {
super.onRecycled(messages: messages, viewModel: viewModel)
// 资源清理
}
}其他
数据结构与转换流程
自定义消息会涉及到 3 种数据结构(KIMMessage、KIMChatMessage、KIMMessageCellState),以及 2 种用于转换这些数据结构的类(KIMChatMessageDataSourceBuilder、KIMChatMessageCellStateBuilder)。理解这些数据结构,以及数据转换的过程,是接入自定义消息必不可少的环节。
下图为数据结构与转换流程的示意图:

① KIMMessage为中台业务模型,是原始的消息结构。其中有一个属性appCustomizeMsg,即为自定义消息:
public class KIMMessage: NSObject, Codable {
/// 自定义消息
@objc public var appCustomizeMsg: KIMAppCustomizeMsg?
}KIMAppCustomizeMsg结构如下:
@objc public class KIMAppCustomizeMsg: NSObject {
/// 自定消息内容
@objc public var content: String = String()
/// 消息子类型,由用户自己定义
@objc public var customizeType: String = String()
/// 消息描述文本,最大长度 5000 字符,该描述信息用于消息关键词检索及消息摘要显示
@objc public var msgDesc: String = String()
}其中属性content,即为发送方发送的自定义消息内容,收发双方需提前约定好此字段的数据结构。
② KIMChatMessage为 ViewModel 列表数据源使用的消息类型,其中有一个属性customData,即可用于存储自定义数据:
open class KIMChatMessage: NSObject {
/// 用户自定义数据
@objc public var customData: Any?
}③ KIMChatMessageDataSourceBuilder则用于将KIMMessage转化为KIMChatMessage。需自定义类实现此协议,从KIMMessage的appCustomizeMsg解析数据,按需进行转换,并存储到KIMChatMessage的customData中。
④ KIMMessageCellState为消息单元格的视图模型,其中有一个属性messageContent用于存储消息内容:
open class KIMMessageCellState: NSObject {
/// 消息内容,包括文本、图片、音频等视图数据。
@objc public let messageContent: KIMMessageCellContent
}KIMMessageCellContent结构如下,包括一个用于存储自定义内容的customContent属性:
open class KIMMessageCellContent: NSObject {
/// 单元格自定义内容
public var customContent: Any?
}⑤ KIMChatMessageCellStateBuilder则用于将KIMChatMessage转化为KIMMessageCellContent。需自定义类实现此协议,从KIMMessageCellContent的customContent中读取数据,按需进行转换,并存储到KIMChatMessage的messageContent.customContent中。
自定义 DataSourceBuilder
基本流程:
自定义结构实现
KIMChatMessageDataSourceBuilder协议定义 lazy 属性,使用
KIMChatUI.shared.servicePlugin.getDefaultService获取获取系统的默认实现实现
buildDataSource协议方法仅处理当前注册的自定义消息
调用默认实现,生成
KIMChatMessage根据收发双方约定的数据结构,从
appCustomizeMsg的content属性中解析消息构建自定义内容,并存储到
KIMChatMessage的customData属性中返回
KIMChatMessage
示例代码:
class YourChatMessageDataSourceBuilder: KIMChatMessageDataSourceBuilder {
public let bodyType: KIMServicePlugin.BodyType
init(bodyType: KIMServicePlugin.BodyType) {
self.bodyType = bodyType
}
/// 第 1 步:获取系统默认的 DataSourceBuilder
private lazy var def: KIMChatMessageDataSourceBuilder? = {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageDataSourceBuilder.self,
bodyType: self.bodyType
)
}()
func buildDataSource(chatModel: KIMChatModel, coreMessage: KIMMessage, prevChatMessage: KIMChatMessage?, viewModel: KIMChatMessageViewModel) -> KIMChatMessage? {
/// 第 2 步:调用默认实现,生成 KIMChatMessage
guard let chatMessage = def?.buildDataSource(chatModel: chatModel, coreMessage: coreMessage, prevChatMessage: prevChatMessage, viewModel: viewModel) else {
return nil
}
/// 第 3 步:仅处理当前注册的自定义消息
guard WeatherMessageManager.default.shouldProcessMessage(coreMessage) else {
return chatMessage
}
/// 第 4 步:从 appCustomizeMsg 的 content 属性中解析消息
/// 此处假设 content 的数据结构为 YourAppCustomizeMsgContent 的 json 字符串
guard let content = coreMessage.appCustomizeMsg?.content,
let contentObject = try? JSONDecoder().decode(YourAppCustomizeMsgContent.self, from: Data(content.utf8)) else {
return chatMessage
}
/// 第 5 步:构建自定义内容,按需进行转换,并存储到 KIMChatMessage 的 customData 中
/// 此处 yourCreateCustomDataFunction 为示例函数
let customData = yourCreateCustomDataFunction(contentObject)
chatMessage.customData = customData
chatMessage.checkStatus = .none /// 如果需要启用当前自定义消息的多选能力,注释本行代码
/// 第 6 步:返回 KIMChatMessage
return chatMessage
}
}//
// YourChatMessageDataSourceBuilder.h
//
#import <Foundation/Foundation.h>
@import KIMKit;
@interface YourChatMessageDataSourceBuilder: NSObject<KIMChatMessageDataSourceBuilder>
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType;
@end//
// YourChatMessageDataSourceBuilder.m
//
#import "YourChatMessageDataSourceBuilder.h"
@import KIMKit;
@import KIMCore;
@interface YourChatMessageDataSourceBuilder ()
@property (strong, nonatomic) id<KIMChatMessageDataSourceBuilder> def;
@property (nonatomic, assign) enum KIMServiceBodyType bodyType;
@end
@implementation YourChatMessageDataSourceBuilder
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType {
self = [super init];
if (self) {
_bodyType = bodyType;
}
return self;
}
- (KIMChatMessage *)buildDataSourceWithChatModel:(KIMChatModel *)chatModel coreMessage:(KIMMessage *)coreMessage prevChatMessage:(KIMChatMessage *)prevChatMessage viewModel:(KIMChatMessageViewModel *)viewModel {
// 第 2 步:调用默认实现,生成 KIMChatMessage
KIMChatMessage *chatMessage = [self.def buildDataSourceWithChatModel:chatModel coreMessage:coreMessage prevChatMessage:prevChatMessage viewModel:viewModel];
if (chatMessage == nil) {
return nil;
}
// 第 3 步:仅处理当前注册的自定义消息
if (![coreMessage.type isEqualToString:@"kim-app-customize"] || ![coreMessage.appCustomizeMsg.customizeType isEqualToString:@"your_customize_type"]) {
return chatMessage;
}
// 第 4 步:从 appCustomizeMsg 的 content 属性中解析消息
// 此处假设 content 的数据结构为 json 字符串
NSString *content = coreMessage.appCustomizeMsg.content;
if (content == nil) {
return chatMessage;
}
NSData *jsonData = [content dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *contentObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error != nil) {
return chatMessage;
}
// 第 5 步:构建自定义内容,按需进行转换,并存储到 KIMChatMessage 的 customData 中
// 此处 yourCreateCustomDataFunction 为示例函数
id customData = [self yourCreateCustomDataFunction:contentObject];
chatMessage.customData = customData;
chatMessage.checkStatus = KIMMessageCheckStatusNone; /// 如果需要启用当前自定义消息的多选能力,注释本行代码
return chatMessage;
}
// 第 1 步:获取系统默认的 DataSourceBuilder
- (id<KIMChatMessageDataSourceBuilder>)def {
if (_def == nil) {
_def = [ObjcBridge getDefaultChatMessageDataSourceBuilderWithBodyType:self.bodyType];
}
return _def;
}
@end注:由于KIMChatUI.shared.servicePlugin.getDefaultService的定义包含泛型,因此无法直接在 Objective-C 代码中调用。这里使用 Swift 类做了一个桥接:
@objcMembers
class ObjcBridge: NSObject {
class func getDefaultChatMessageDataSourceBuilder(bodyType: KIMServicePlugin.BodyType) -> KIMChatMessageDataSourceBuilder? {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageDataSourceBuilder.self,
bodyType: bodyType
)
}
}自定义 CellStateBuilder
基本流程:
自定义结构实现
KIMChatMessageCellStateBuilder协议定义 lazy 属性,使用
KIMChatUI.shared.servicePlugin.getDefaultService获取获取系统的默认实现实现
buildCellState协议方法仅处理当前注册的自定义消息
调用默认实现,生成
KIMMessageCellState读取
KIMChatMessage的customData,按需进行转换,并将转换结果用于构建KIMMessageCellState返回
KIMMessageCellState
示例代码:
class YourChatMessageCellStateBuilder: KIMChatMessageCellStateBuilder {
public let bodyType: KIMServicePlugin.BodyType
init(bodyType: KIMServicePlugin.BodyType) {
self.bodyType = bodyType
}
/// 第 1 步:获取系统默认的 CellStateBuilder
private lazy var defaultBuilder: KIMChatMessageCellStateBuilder? = {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageCellStateBuilder.self,
bodyType: self.bodyType
)
}()
func buildCellState(chatMessage: KIMChatMessage, viewModel: KIMChatMessageViewModel) -> KIMMessageCellState? {
/// 第 2 步:调用默认实现,生成 KIMMessageCellState
guard let cellState = defaultBuilder?.buildCellState(chatMessage: chatMessage, viewModel: viewModel) else {
return nil
}
/// 第 3 步:仅处理当前注册的自定义消息
guard WeatherMessageManager.default.shouldProcessChatMessage(chatMessage) else {
return chatMessage
}
/// 第 4 步:读取 KIMChatMessage 的 customData
/// 此处 YourChatMessageCustomData 为 chatMessage.customData 的数据结构
guard let content = chatMessage.customData as? YourChatMessageCustomData else {
return nil
}
// 第 5 步:构建 KIMMessageCellState 的自定义内容
// 此处的 yourCreateCustomContentFunction 为示例函数
let costomContent = yourCreateCustomContentFunction(content)
// 第 6 步:构建 KIMMessageCellState 并返回
let messageContent = KIMMessageCellContent(cellContentType: .custom, customContent: costomContent)
return KIMMessageCellState(baseInfo: cellState.baseInfo, messageContent: messageContent, refMsgContent: cellState.refMsgContent, layoutOptions: cellState.layoutOptions)
}
}//
// YourChatMessageCellStateBuilder.h
//
#import <Foundation/Foundation.h>
@import KIMKit;
@interface YourChatMessageCellStateBuilderObjc: NSObject<KIMChatMessageCellStateBuilder>
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType;
@end//
// YourChatMessageCellStateBuilder.m
//
#import "YourChatMessageCellStateBuilder.h"
@import KIMKit;
@import KIMCore;
@interface YourChatMessageCellStateBuilder ()
@property (strong, nonatomic) id<KIMChatMessageCellStateBuilder> def;
@property (nonatomic, assign) enum KIMServiceBodyType bodyType;
@end
@implementation YourChatMessageCellStateBuilder
- (instancetype)initWithBodyType:(enum KIMServiceBodyType)bodyType {
self = [super init];
if (self) {
_bodyType = bodyType;
}
return self;
}
- (KIMMessageCellState *)buildCellStateWithChatMessage:(KIMChatMessage *)chatMessage viewModel:(KIMChatMessageViewModel *)viewModel {
// 第 2 步:调用默认实现,生成 KIMMessageCellState
KIMMessageCellState *cellState = [self.def buildCellStateWithChatMessage:chatMessage viewModel:viewModel];
if (cellState == nil) {
return nil;
}
// 第 3 步:仅处理当前注册的自定义消息
if (![chatMessage.coreMessage.type isEqualToString:@"kim-app-customize"] || ![chatMessage.coreMessage.appCustomizeMsg.customizeType isEqualToString:@"weather_2"]) {
return cellState;
}
/// 第 4 步:读取 KIMChatMessage 的 customData
/// 此处 YourChatMessageCustomData 为 chatMessage.customData 的数据结构
if (chatMessage.customData == nil || ![chatMessage.customData isKindOfClass:[YourChatMessageCustomData class]]) {
return cellState;
}
WeatherViewModelCustomContent *customData = chatMessage.customData;
// 第 5 步:构建 KIMMessageCellState 的自定义内容
// 此处的 yourCreateCustomContentFunction 为示例函数
id costomContent = yourCreateCustomContentFunction(customData)
// 第 6 步:构建 KIMMessageCellState 并返回
KIMMessageCellContent *messageContent = [[KIMMessageCellContent alloc] initWithCellContentType:KIMMessageCellContentTypeCustom noticeContent:nil attributedTextContent:nil pictureContent:nil videoContent:nil voiceContent:nil fileContent:nil mergedMsgsCardContent:nil recallContent:nil cardContent:nil customContent:customContent];
return [[KIMMessageCellState alloc] initWithBaseInfo:cellState.baseInfo messageContent:messageContent refMsgContent:cellState.refMsgContent layoutOptions:cellState.layoutOptions];
}
// 第 1 步:获取系统默认的 CellStateBuilder
- (id<KIMChatMessageCellStateBuilder>)def {
if (_def == nil) {
_def = [ObjcBridge getDefaultChatMessageCellStateBuilderWithBodyType:self.bodyType];
}
return _def;
}
@end注:由于KIMChatUI.shared.servicePlugin.getDefaultService的定义包含泛型,因此无法直接在 Objective-C 代码中调用。这里使用 Swift 类做了一个桥接:
@objcMembers
class ObjcBridge: NSObject {
class func getDefaultChatMessageCellStateBuilder(bodyType: KIMServicePlugin.BodyType) -> KIMChatMessageCellStateBuilder? {
KIMChatUI.shared.servicePlugin.getDefaultService(
KIMChatMessageCellStateBuilder.self,
bodyType: bodyType
)
}
}异步任务管理
自定义消息可能会涉及到异步操作,比如:发送的消息自定义内容中,仅包含资源 id,接收端需要通过此 id 发起 HTTP 请求,才能获得完整的资源信息并展示。上述过程的 HTTP 请求就是一种异步请求,SDK 针对异步请求提供了接入方案:
自定义 Task
在自定义 manager时,需要提供继承自KIMAsyncTask的自定义类,此类即用于管理消息涉及到的异步任务(注:如果不涉及到异步请求,直接提供KIMAsyncTask即可)。
自定义 Task 需要重载父类的 3 个方法:
| 方法 | 含义 | 使用说明 |
|---|---|---|
| main() | 任务启动 | 在其中发起异步请求 |
| finish(Error?) | 标记任务完成 | main 中发起的异步请求结束后,需调用此方法标记任务已完成;如果请求失败,需传入 Error |
| cancel() | 任务取消 | 在其中取消异步操作,并调用 super.cancel(),以释放资源 |
使用案例:
class YourAsyncTask: KIMAsyncTask {
private let resourceId: String
private var task: URLSessionDataTask?
/// 存储请求结果
private var result: String?
init(resourceId: String) {
self.resourceId = resourceId
super.init()
}
override func main() {
guard let url = URL(string: "https://api.yourserver.com/resource/\(resourceId)") else {
return
}
// 重载 main,发起异步请求
let request = URLRequest(url: url)
request.httpMethod = "GET"
task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error {
// 如果出现错误,调用 finish 传入方法
self.finish(error)
return
}
if let data = data {
self.result = String(data: data, encoding: .utf8)
}
// 如果请求成功,同样调用 finish
self.finish()
}
task?.resume()
}
override func cancel() {
// 重载 cancel 方法,取消任务,释放资源
task?.cancel()
task = nil
super.cancel()
}
}注:由于 Swift 编译器的限制,目前无法在 Objective-C 中继承 Swift 类,因此 KIMAsyncTask 子类只能使用 Swift 来编写
KIMAsyncTask公共属性如下:
| 属性 | 类型 | 说明 |
|---|---|---|
| error | Error? | 异步任务的错误记录。此属性为只读属性,只能由 finish(Error?) 写入 |
| timeout | TimeInterval? | 异步任务超时时间。默认 10 秒,可自行设置,最大可设置为 10 秒 |
| state | KIMAsyncTaskState | 异步任务当前状态 |
KIMAsyncTaskState取值如下:
enum KIMAsyncTaskState {
/// 等待发起
case pending
/// 正在执行
case executing
/// 执行结束
case finished
/// 取消
case cancelled
}状态之间的转换流程图如下:

使用 Task
定义好KIMAsyncTask后,需重载KIMCustomChatMessageManager的两个方法,以发起 task:
createTask:此方法会在需要发起异步任务的时候,由 SDK 自动调用updateMessageWhenFetched:此方法会在异步任务成功后,由 SDK 自动调用
class YourMessageManager: KIMCustomChatMessageManager<YourMessageCell, YourAsyncTask, YourCacheType> {
override func createTask(message: KIMChatMessage) -> WeatherTask? {
guard let content = message.customData as? YourKIMChatMessageCustomContent else {
return nil
}
/// 根据 customMessageContent 创建 task 实例,并返回
let task = YourAsyncTask(
resourceId: content.resourceId
)
return task
}
override func updateMessageWhenFetched(message: KIMChatMessage, task: YourAsyncTask) {
guard let customData = message.customData as? YourKIMChatMessageCustomContent else {
return
}
/// 从 task 中获取请求结果数据,存储到 KIMChatMessage 的 customData 中
customData.skycon = task.skycon
customData.state = (task.skycon != nil) ? .succeed : .failed
message.customData = customData
}
}manager 提供了如下的方法用于任务管理:
/// 启动异步任务
/// SDK 会在合适的时机自动启动任务,使用方一般无需主动调用
func addTask(message: KIMChatMessage, task: T)
/// 获取 KIMChatMessage 关联的异步任务
func getTask(message: KIMChatMessage) -> T?
/// 取消异步任务
/// SDK 会在合适的时机自动取消任务,使用方一般无需主动调用
func cancelTask(message: KIMChatMessage)
/// 取消所有异步任务
/// SDK 会在会话页面销毁时,自动调用此方法,使用方一般无需主动调用
func cancelAllTasks()缓存管理
在 自定义 task 时,在YourAsyncTask中定义了存储请求结果的变量result:
class YourAsyncTask: KIMAsyncTask {
/// 存储请求结果
private var result: String?
}这其实就是一种数据缓存。不过,在真实业务需求中,除了和异步请求相关联的数据需要缓存,可能还有其他的数据缓存需求。manager 提供了如下的缓存 API:
| API | 使用说明 |
|---|---|
| getCache(cid: String) -> CacheType? | 根据消息 cid 获取缓存 |
| saveCache(cid: String, data: CacheType?) | 写缓存 |
| removeCache(cid: String) | 清楚缓存数据 |
| removeAllCache() | 清除全部缓存数据 |
注:
上述
CacheType为自定义 manager时传入的如果暂时不需要缓存能力,可以直接传
Any此缓存为内存缓存,框架暂未提供磁盘缓存能力
调用举例:
/// 写缓存
YourManager.shared.saveCache(cid: message.cid, data: yourData)
/// 读缓存
let data = YourManager.shared.getCache(message.cid)
/// 清除缓存
YourManager.shared.removeCache(message.cid)
/// 清除所有缓存
YourManager.shared.removeAllCache()Objective-C工程接入方案
自定义消息框架是使用 Swift 语言编写的,如果需要在 Objective-C 工程中接入此框架,请参考如下的兼容性说明:
| 数据类型 | 涉及操作 | 语言兼容性 | 原因 |
|---|---|---|---|
| KIMCustomChatMessageManager | 子类化 | 仅支持使用 Swift 定义子类 | Objective-C 类无法继承 Swift 类 |
| KIMMessageBaseCell | 子类化 | Swift 或 Objective-C | |
| KIMChatMessageDataSourceBuilder | 实现协议 | Swift 或 Objective-C | |
| KIMChatMessageCellStateBuilder | 实现协议 | Swift 或 Objective-C | |
| KIMChatMessageOperationMenuService | 实现协议 | Swift 或 Objective-C | |
| KIMQuickReplyMenuService | 实现协议 | Swift 或 Objective-C | |
| KIMAsyncTask | 子类化 | 仅支持使用 Swift 定义子类 | Objective-C 类无法继承 Swift 类 |
即仅KIMCustomChatMessageManager、KIMAsyncTask的子类化需要使用 Swift 完成,其他都可以使用 Objective-C 来编写。
同时,由于KIMCustomChatMessageManager的定义使用到了泛型,因此无法直接在 Objective-C 代码中使用其子类,可以采取如下的方式:
使用 Swift 子类化
KIMCustomChatMessageManager使用 Swift 定义一个用于桥接的类,继承自
NSObject,并标记为@objc在此类中封装 manager 的各种接口,并使用
@objc对需要暴露的 API 进行标记最后,在 Objective-C 代码中使用该类
举例:
@objc class YourMessageManagerForObjc: NSObject {
/// 封装 Swift 方法,用于对 Objc 代码暴露接口
@objc class func register() {
let manager = YourMessageManager(
customizeType: "unique_message_customize_type",
dataSourceBuilderBlock: { _ in YourChatMessageDataSourceBuilder() },
cellStateBuilderBlock: { _ in YourChatMessageCellStateBuilder() },
operationMenuServiceBlock: { _ in YourChatMessageOperationMenuService() }
)
manager.register()
}
@objc class func getCache(cid: String) -> Any? {
YourMessageManager.default.getCache(cid: cid)
}
}然后在 Objective-C 中使用上述类:
#import "Your-Project-Swift.h"
+ (void)test {
[YourMessageManagerForObjc register];
id result = [YourMessageManagerForObjc getCacheWithCid:@"23123521"];
}工程结构参考:
- YourMessageManager.swift
- YourMessageManagerForObjc.h
- YourMessageManagerForObjc.m
- YourMessageCell.h
- YourMessageCell.m
- YourChatMessageDataSourceBuilder.h
- YourChatMessageDataSourceBuilder.m
- YourChatMessageCellStateBuilder.h
- YourChatMessageCellStateBuilder.m
// 以下为按需实现的结构
- YourAsyncTask.swift
- YourChatMessageOperationMenuService.h
- YourChatMessageOperationMenuService.m
- YourQuickReplyMenuService.h
- YourQuickReplyMenuService.m