描述
此仓库包含一个通用的聊天框架,可以轻松扩展以在任何包含标准内聊组件的项目中使用。此库被分为多个模块(层)。每个模块都是一个独立的框架目标,因此相互之间的实现细节可以隐藏。每个模块也是一个独立的项目。
安装
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
方法中实例化。
示例
请参考示例应用以了解如何使用这个库。
架构
该库包含四个模块
- 聊天
- 聊天核心
- 聊天网络Firestore
- 聊天UI
请参阅此图以了解架构的视觉概述
聊天核心 - 关键模型和协议
ChatCore
这是UI可以用于所有聊天操作的主要类。它包含了所有必要的函数,并实现了 ChatCoreServicing
协议。
DataPayload
该类携带从监听器接收到的数据(例如,会话集合)以及是否已达数据末尾的信息。这对于UI来说很有用,例如,可以在“加载更多”按钮隐藏时使用。未来可能还会包含一些其他元数据。
ChatNetworkingFirestore - Firestore网络层(可选)
如果您的项目使用Firestore作为聊天数据库,可以使用此Pod。如果您需要使用其他API,可以编写自己的网络层。请参阅编写自定义网络层
ChatUI - 使用MessageKit的UI(可选)
简单UI实现,用于测试。大多数项目可能会选择编写自己的UI。请参阅编写自定义UI层。
Chat – 胶水
如果您想使用现有的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 使用
监听器
由于聊天是一种实时功能,客户端需要尽快通知任何新的数据更新。这就是为什么使用持久连接来获取对话和消息的原因。每当您调用 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...等
- 输入指示器
- 通知