STRVChat 0.0.8

STRVChat 0.0.8

Jan Schwarz 维护。



STRVChat 0.0.8

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

iOS 聊天组件 Swift 5.3

描述

该仓库包含一个通用的聊天框架,可以轻松扩展到用于任何包含标准应用聊天组件的项目。这个库分为多个模块(层)。每个模块都是一个独立的框架目标,因此可以实现细节彼此隐藏。每个模块也是一个独立的 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方法中实例化。

示例

查看a href="ChatApp/示例应用以了解如何使用此Pod。

架构

该库由四个模块组成

  • 聊天
  • 聊天核心
  • 聊天网络Firestore
  • 聊天UI

参见此图,以了解架构的总体视图

ChatCore - 关键模型和协议

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层和网络层

以下以上述提到的完整PumpkinPie解决方案为例。

首先,您需要实现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.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 可以正确地做出响应。核心包含一个可达性观察器,用于检查网络连接的可用性。

可用状态
  • 初始 核心创建后
  • 加载中 初始化后,核心尝试连接到服务提供商并设置所有内容
  • 已连接 所有加载工作都完成,核心准备好执行其工作
  • 连接中 当核心被通知网络断开时

缓存未发送的消息

当核心发送消息时,它会自动存储到本地的安全缓存(密钥链)中。发送消息后(例如,后台任务),它会从缓存中删除。如果因任何原因发送失败,用户可以在用户界面中有机会重试或删除消息。

功能

对话

  • 对话列表
  • 当对话更新时收到通知
  • 实现后台抓取以实现重试机制
  • 在线状态指示器

消息

  • 当创建/更新消息时收到通知
  • 已读收据 - 在另一用户阅读的最后一条消息旁边显示一个带有时间和日期的标志
  • 缓存无法发送的消息
  • 发送消息的重试机制
  • 即使用户将应用程序发送到后台,也要继续发送消息
  • 删除一条消息

用户

  • 名称
  • 头像

支持的消息类型

  • 文本
  • 照片
  • 视频
  • 位置

未来功能

这些功能将在开发后期添加,但框架必须在考虑它们的前提下开发。

  • 持久性
  • 管理
  • 扩展 - RxSwift, PromiseKit 等
  • 输入指示器
  • 通知

参考

现有解决方案

Pod中使用的开源UI组件