自定义消息
协作中台 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