描述
此仓库包含一个通用的聊天框架,可以根据需要轻松扩展以用于包含标准应用内聊天组件的任何项目中。此库分为多个模块(层)。每个模块都是一个独立的框架目标,因此可以隐藏相互的实现细节。每个模块都具有自己的 pod。
安装
CocoaPods
要使用 CocoaPods 将聊天组件集成到您的 Xcode 项目中,请在您的 Podfile
中指定它。
source 'https://cdn.cocoapods.org/'
target 'TARGET_NAME' do
# For plug&play solution with predefined MessageKit UI and Firestore backend
pod 'STRVChat'
# If you want to use just some of the layers
pod 'STRVChat/Core'
pod 'STRVChat/NetworkingFirestore'
pod 'STRVChat/UI'
end
初始化
默认聊天组件实现使用后台获取和计划任务,需要在应用程序代理的 didFinishLaunchingWithOptions
方法中进行实例化。
示例
请查看 示例应用程序 了解如何使用此 pod。
架构
该库由四个模块组成
- 聊天
- ChatCore
- ChatNetworkingFirestore
- ChatUI
请参阅此图了解架构的总体视图
ChatCore - 重要的模型和协议
ChatCore
这是一个主要类,UI 可以用它执行所有聊天操作。它包含所有必需的方法,并实现了 ChatCoreServicing
协议。
DataPayload
该类承载从监听器(例如对话集合)接收到的数据,以及是否已达到数据末尾的信息。这对于UI能够隐藏“加载更多”按钮等操作非常有用。未来它可能包含一些更多的元数据。
ChatNetworkingFirestore - Firestore网络层(可选)
如果你的项目使用Firestore作为聊天数据库,你可以使用此pod。如果需要使用其他API,你可以编写自己的网络层。请参阅编写自定义网络层。
ChatUI - 使用MessageKit的UI(可选)
这是一个简单的UI实现,用于测试。大多数项目可能需要编写自己的UI。请参阅编写自定义UI层。
Chat – Glue
当你想要使用现有的ChatCoreServicing
、ChatNetworkServicing
和ChatUIServicing
实现,不需要任何自定义功能时,可以使用库中的这部分代码。它包含所有部分正常工作的粘合代码。目前它只包含Pumpkin Pie项目的聊天解决方案,但如果你想要从不同的组件构建聊天,那就非常简单。请参阅粘合UI层和网络层。
编写自定义UI层
提供的UI层相对简单,不提供任何自定义项,因此在项目中通常需要创建自己的UI层。所需做的就是向ChatCore
类提供一个实现了ChatUIModels
的实例,该实例指定了应提供给您完成处理器的哪些模型。
class ChatCore<Networking: ChatNetworkServicing, Models: ChatUIModels>: ChatCoreServicing
public protocol ChatUIModels {
associatedtype CUI: ConversationRepresenting
associatedtype MUI: MessageRepresenting
associatedtype MSUI: MessageSpecifying
associatedtype USRUI: UserRepresenting
}
编写自定义网络层
实现自定义网络层所需的是遵从ChatNetworkServicing
协议,并在下一章中提供相应的实现给ChatCore
。
连接UI层和网络层
以上提到的完整Pumpkin Pie解决方案可作为示例。
首先,您需要实现ChatNetworkServicing
、ChatCoreServicing
和ChatUIServicing
。从现在起,让我们假设您想使用ChatCore
作为ChatCoreServicing
的实现,因为它足够通用,可以与任何ChatNetworkServicing
和ChatUIServicing
的实现一起工作。在Pumpkin Pie中,这些实现是ChatNetworkFirestore
和ChatUI
。
其次,您需要使所有网络模型遵从ChatUIConvertible
,例如
extension ConversationFirestore: ChatUIConvertible {
public var uiModel: Conversation {
// return Conversation instance created using data from `self`
}
public init(uiModel: Conversation) {
// call self.init using data from `uiModel`
}
}
并使所有UI模型遵从ChatNetworkingConvertible
,例如
extension Conversation: ChatNetworkingConvertible {
public typealias NetworkingModel = ConversationFirestore
}
第三,您需要使您的ChatUIServicing
实现与ChatCore
兼容,简单像这样
public class ChatUI<Core: ChatUICoreServicing>: ChatUIServicing {
...
}
public protocol ChatUICoreServicing: ChatCoreServicing where C == Conversation, M == MessageKitType, MS == MessageSpecification, U == User { }
extension ChatCore: ChatUICoreServicing where Models.CUI == Conversation, Models.MSUI == MessageSpecification, Models.MUI == MessageKitType, Models.USRUI == User { }
接下来,您需要将您的ChatUIServicing
实现封装在ChatInterfacing
中,以管理不同场景中的不同UI实例
public class PumpkinPieInterface: ChatInterfacing {
public let identifier: ObjectIdentifier
public let uiService: ChatUI<...>
public weak var delegate: Delegate?
public var rootViewController: UIViewController {
uiService.rootViewController
}
init(...) {...}
}
最后,通过实现ChatSpecifying
协议来将所有内容连接在一起
public class PumpkinPieChat: DefaultChatSpecifying {
public typealias UIModels = ChatModelsUI
public typealias Networking = ChatNetworkingFirestore
public typealias Core = ChatCore<ChatNetworkingFirestore, UIModels>
public typealias Interface = PumpkinPieInterface
public init(...) {...}
func interface(with identifier: ObjectIdentifier) -> Interface {...}
func runBackgroundTasks(completion: @escaping (UIBackgroundFetchResult) -> Void) {...}
func resendUnsentMessages() {...}
func setCurrentUser(userId: EntityIdentifier, name: String, imageUrl: URL?) {...}
}
Chat核心使用
监听器
由于聊天是一项实时功能,客户端需要尽快通知任何新的数据更新。这就是为什么通过持久连接来获取对话和消息。每次你调用core.listenToConversations
或core.listenToMessages
时,你会收到一个ChatListener
(目前只是一个String
别名)实例,你必须将其存储在某处。当你准备好停止监听这组特定数据时,调用core.remove(listener: ChatListener)
以确保没有不必要的网络连接遗留。
发送消息
core.send(message: .text(message: "Hello!"), to: currentChatId) { result in
// Called after successfuly sending a message
switch result {
case .success(let message):
print(message.id)
case .failure(let error):
print(error)
}
}
监听对话
core.listenToConversations { result in
// Called on every conversations update
switch result {
case .success(let payload):
// payload contains current list of fetched conversations
// as well as some additional data (see `DataPayload` for more info)
case .failure(let error):
print(error)
}
}
监听对话消息
core.listenToMessages(conversation: conversationId) { result in
// Called on every messages update
switch result {
case .success(let payload):
// payload contains current list of fetched conversations
// as well as some additional data (see `DataPayload` for more info)
case .failure(let error):
print(error)
}
}
分页
当你需要加载对话或消息的下一页时,只需分别调用core.loadMoreConversations
或core.loadMoreMessages
。你提供的现有回调会在调用core.listenTo...
方法时以更新后的数据集被调用。(所以第一次调用core.listenToConversations
时,你会收到1 * n个对话,在调用core.loadMoreConversations
后,你会收到2 * n个对话)。
任务管理
为了使聊天核心组件更为完善,我们通过任务管理器对其进行了增强。任务管理器类使聊天核心能够调用带有额外属性的调用,并且处理所有背后的逻辑。使用任务管理会影响聊天核心本身的完成处理。
可用的属性
- 初始化后
- 后台任务
- 后台线程
- 重试(带有重试类型有限/无限)
初始化后
一些调用(例如加载会话)可以进入任务管理器的队列,尽管聊天核心尚未连接。这些任务保持在队列中,在核心加载完成后执行。
后台任务
归因于后台任务的调用将在应用程序进入后台时继续尝试。最初所有任务都连接到应用程序的 beginBackgroundTaskWithName:expirationHandler:
方法。次要的,如果iOS版本是13+并且队列中仍有未完成的任务,则使用 BGScheduledTask
在一段时间后激活应用程序,以重试执行这些任务。如果聊天在较旧的iOS版本上运行,则使用后台获取。
后台线程
指定后台线程的任务将在其专用的后台线程中运行。
重试
为了允许任务重试,有两种方法。有限的尝试次数有限,无限。无限类型用于整个聊天核心的初始加载。尝试次数默认为3的有限重试类型用于发送消息。请注意,只有在处理重试时才会处理网络错误,其他错误不计。
调用示例
下面是发送消息任务方法的示例。任务管理器封装发送消息的网络调用,并对它应用了一些属性。使用重试属性时,需要控制 taskCompletion 回调函数的响应,如示例中所述的失败情况。
taskManager.run(attributes: [.backgroundTask, .afterInit, .backgroundThread, .retry(.finite())]) { [weak self] taskCompletion in
let mess = Networking.MS(uiModel: message)
self?.networking.send(message: mess, to: conversation) { result in
switch result {
case .success(let message):
_ = taskCompletion(.success)
completion(.success(message.uiModel))
case .failure(let error):
if taskCompletion(.failure(error)) == .finished {
completion(.failure(error))
}
}
}
}
聊天核心状态
当创建聊天核心时,会有几个状态,这些状态在其生命周期内可能会改变。可以观察这些变化,以便 UI 能够正确响应。核心包含一个可达性观察者,用于检查网络连接的可用性。
可用状态
- 初始状态 核心创建后
- 加载中 初始化后,核心尝试连接到服务提供商并设置所有东西
- 已连接 所有加载工作完成后,核心准备好执行其任务
- 正在连接 当核心通知网络已断开时
缓存未发送的消息
当核心发送消息时,它自动存储到本地的安全缓存(密钥链)。消息发送后(例如,后台任务)将其从缓存中删除。如果发送失败,用户在 UI 中有重试或删除消息的机会。
功能
会话
- 会话列表
- 在会话更新时发送通知
- 实现后台获取来重试机制
- 在线状态指示器
消息
- 在创建/更新消息时发送通知
- 已读回执 - 在其他用户读到最后消息旁边显示带有时间和日期的标志
- 缓存未能发送的消息
- 发送消息的重试机制
- 即使在用户将应用置于后台时也继续发送消息
- 删除消息
用户
- 姓名
- 头像
支持的消息类型
- 文本
- 照片
- 视频
- 位置
未来特性
这些功能将在开发的后期阶段添加,但必须考虑到它们来开发框架。
- 持久性
- 管理
- 扩展 - RxSwift, PromiseKit...等
- 输入指示器
- 通知