描述
此仓库包含一个通用聊天框架,可以被轻松扩展以用于任何包含标准应用内聊天组件的项目。此库分为多个模块(层)。每个模块都是一个独立的框架目标,因此可以隐藏彼此的实现细节。每个模块也是自己的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
方法中实例化。
示例
见样例应用,了解如何使用本库。
架构
该库由四个模块组成
- 聊天
- 聊天核心
- 聊天网络Firestore
- 聊天 UI
请参阅此图,以了解架构的视觉概述
ChatCore - 核心模型和协议
ChatCore
UI可以使用的主要类,用于所有聊天操作。它包含所有必要的方发,并实现了ChatCoreServicing
协议。
DataPayload
此类携带从监听器(例如会话集合)接收的数据以及数据是否已到达末尾的信息。这对UI隐藏“加载更多”按钮等非常有用。未来可能包含更多元数据。
ChatNetworkingFirestore - Firestore 网络层(可选)
如果你的项目使用 Firestore 作为聊天数据库,可以使用此 pod。如果需要使用其他 API,可以编写自己的网络层。请参阅编写自定义网络层。
ChatUI - 使用 MessageKit 的 UI(可选)
简单的 UI 实现,用于测试。大多数项目可能希望编写自己的 UI。请参阅编写自定义 UI 层。
Chat - 粘合剂
如果你想使用现有的ChatCoreServicing
、ChatNetworkServicing
和ChatUIServicing
实现,并且不需要自定义任何功能,可以使用库的这一部分。它包含所有组成部分正常工作所需的粘合代码。目前它只包含 Pump:n 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个会话)。
任务管理
为了使聊天核心组件考虑得更加周全,使用了任务管理器进行了增强。任务管理器类允许聊天核心进行具有额外属性的调用,并处理所有背后的逻辑。使用任务管理会影响聊天核心本身的对任务完成的处理。
支持的属性
- 初始化后
- 后台任务
- 后台线程
- 重试(具有有限/无限重试类型的重试)
初始化后
一些调用(如加载对话)可以进入任务管理器的队列,即使聊天核心尚未连接。这些任务将在队列中保留,并在核心加载后执行。
后台任务
归因于后台任务的调用将在应用进入后台后尝试继续。最初,所有任务都与应用的《code>beginBackgroundTaskWithName:expirationHandler:方法挂钩。其次,如果iOS版本为13+且有未完成的任务在队列中,则使用《code>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等
- 输入指示
- 通知