STRVChatCore 0.0.13

STRVChatCore 0.0.13

Jan SchwarzDaniel Pecher 维护。



  • Jan Schwarz, Tomáš Čejka, Daniel Pecher 和 Mireya Orta

iOS 聊天组件 Swift 5.3

描述

此仓库包含一个通用的聊天框架,可以轻松扩展以在任何包含标准内聊组件的项目中使用。此库被分为多个模块(层)。每个模块都是一个独立的框架目标,因此相互之间的实现细节可以隐藏。每个模块也是一个独立的项目。

安装

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 – 胶水

如果您想使用现有的ChatCoreServicingChatNetworkServicingChatUIServicing实现,且不需要任何自定义功能,可以使用库的这个部分。它包含所有部分正常工作所需的胶水代码。目前,它仅包含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 解决方案为例。

首先,您需要实现 ChatNetworkServicingChatCoreServicingChatUIServicing。从现在开始,假设您想使用 ChatCore 作为 ChatCoreServicing 的实现,因为它足够通用,可以与任何 ChatNetworkServicingChatUIServicing 的实现组合。在 Pumpkin Pie 中,这些实现是 ChatNetworkFirestoreChatUI

其次,您需要使所有网络模型遵守 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.listenToConversationscore.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.loadMoreConversationscore.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...等
  • 输入指示器
  • 通知

参考

现有解决方案

Pod中使用到的开源UI组件