SendBirdSyncManager 1.1.43

SendBirdSyncManager 1.1.43

Tez ParkJed GyeongYoung HwangYoung Hwangwoo 维护。



  • Jed Gyeong 和 Woo

Sendbird SyncManager for iOS

Platform Languages CocoaPods Carthage compatible Commercial License

目录

  1. 简介
  2. 开始之前
  3. 入门指南
  4. 实现指南

简介

Sendbird SyncManager for iOS 是一个基于Chat SDK的附加组件,通过事件驱动结构将本地数据存储与 Sendbird 服务器中的聊天数据同步,优化了用户缓存体验。

它的工作原理

SyncManager 利用本地缓存并同步本地存储与 Sendbird 服务器间的聊天数据。通过事件驱动结构处理操作,该附加组件提供了简化的 Chat SDK 集成和更好的用户体验。

操作

  • 后台同步 发生在有连接的情况下,会自动将从 Sendbird 服务器获取的数据存储到本地缓存中。
  • 实时同步 持续进行;它识别、存储和传送从 WebSocket 连接接收到的实时事件。
  • 离线模式 确保在离线模式下客户端应用程序可以正常运行,这意味着即使没有后台同步,视图也可以显示缓存的数据。

关于 Sendbird SyncManager for iOS 的更多信息

SyncManager for iOS 文档 中了解更多关于 Sendbird SyncManager for iOS 的信息。如果您对错误和功能请求有任何评论或问题,请访问 Sendbird 社区


开始之前

本节展示了您需要检查的使用 Sendbird SyncManager for iOS 的前提条件。

要求

SyncManager for iOS 的最低要求是

  • iOS 8.0+
  • Sendbird Chat SDK for iOS v3.0.178+

入门指南

本节提供了您在 Sendbird SyncManager for iOS 入门所需的信息。

试用示例应用程序

下载示例应用程序以测试 SyncManager for iOS 的核心功能。

注意:测试我们的 SyncManager 最快的方式是在我们的示例应用程序之上构建你的聊天应用程序。请确保将示例应用程序的应用程序 ID 更改为自己的。前往从仪表板创建 Sendbird 应用程序部分了解详细信息。

从 CocoaPods 安装 SendBirdSyncManager 框架

将以下内容添加到你的 Xcode 的 Podfile。

platform :ios, '8.0'
use_frameworks!

target YOUR_PROJECT_TARGET do
  pod 'SendBirdSyncManager'
end

通过 CocoaPods 安装 SendBirdSyncManager 框架。

pod install

通过 CocoaPods 更新 SendBirdSyncManager 框架。

pod update SyncManager

现在,你通过检查 YOUR_PROJECT.xcworkspace 可以看到已安装的 SendBirdSyncManager 框架。

注意SendBirdSyncManager 依赖于 SendBird SDK。如果你安装了 SendBirdSyncManagerCocoaPods 会自动安装 SendBird SDK。并且,SendBird SDK 的最小版本是 3.0.203

从 Carthage 安装 SendBirdSyncManager 框架

  1. github "sendbird/sendbird-syncmanager-ios" 添加到你的 Cartfile
  2. 运行 carthage update
  3. 转到 Xcode 项目的 通用 设置。在 Finder 中打开 <YOUR_XCODE_PROJECT_DIRECTORY>/Carthage/Build/iOS 并将 SendBirdSyncManager.framework 拖到 Xcode 的 已嵌入的二进制文件 部分。确保选择了 如有必要则复制项目,并点击 完成

实现指南

初始化

SBSMSyncManager是一个单例类。当 SBSMSyncManager初始化时,会创建一个Database实例。因此,如果想尽快初始化Database,可以在获取到用户ID后立即调用setup(_:)。我们建议在application(_:didFinishLaunchingWithOptions:)中执行。

// swift
// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // after getting user's ID or login
    SBSMSyncManager.setup(withUserId: userId)
}
// objective-c
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // after getting user's ID or login
    [SBSMSyncManager setupWithUserId:userId];
}];

集合

Collection是用于管理与视图相关的Sendbird对象(如SBDGroupChannelSBDBaseMessage)的容器。SBSMChannelCollection通常附加到频道列表视图控制器,而SBSMMessageCollection则附加到消息列表视图控制器。Collection的主要目的是:

  • 监听数据事件并将其作为视图事件交付。
  • 从缓存或Sendbird服务器获取数据,并将数据作为视图事件交付。

每个集合都有自己的事件订阅者和数据获取者。事件订阅者监听数据事件,以便将这些数据更新应用到视图中;而数据获取者则从缓存或服务器加载数据,并将数据发送给事件处理器。

- 频道集合

频道是当前进行聊天的可变数据。由于许多应用程序按最后一条消息对频道进行排序,因此频道最后一条消息的未读消息数量频繁更新,每个频道的位置也会发生剧烈变化。因此,SBSMChannelCollection主要依赖于服务器同步。以下是SBSMChannelCollection同步数据的步骤:

  1. 它从缓存中加载频道,并将它们显示在视图中。
  2. 然后,它从Sendbird服务器获取最新的频道,并将其与视图中的频道合并。
  3. 每次在fetch(_:)中调用以查看以前的频道时,它都会从Sendbird服务器获取数据。

注意:频道数据同步机制可能会在以后更改。

SBSMChannelCollection需要一个来自Sendbird SDKSBDGroupChannelListQuery实例,因为它是将查询绑定到集合中。然后,集合使用查询过滤数据。下面是创建新的SBSMChannelCollection实例的代码。通常在分组频道列表视图控制器的viewDidLoad()中创建频道集合。

// swift
override func viewDidLoad() {
    let query: SBDGroupChannelListQuery? = SBDGroupChannel.createMyGroupChannelListQuery()
    // limit, order, ... setup your query here. 
    let channelCollection: SBSMChannelCollection? = SBSMChannelCollection.init(query: query)
    self.channelCollection? = channelCollection // Recommands to set a property of view controller
}
// objective-c
- (void)viewDidLoad {    
    SBDGroupChannelListQuery *query = [SBDGroupChannel createMyGroupChannelListQuery];
    // limit, order, ... setup your query here. 
    SBSMChannelCollection *channelCollection = [SBSMChannelCollection collectionWithQuery:query];
    self.channelColletion = channelCollection; // Recommands to set a property of view controller
}

如果视图已关闭,即集合过时且不再使用,则显式删除集合。在视图控制器中,这将在deinit(dealloc)中完成。

// swift
deinit {
    channelCollection?.delegate = nil
    channelCollection?.remove()
}
// objective-c
- (void)dealloc {
    if (channelCollection != nil) {
        channelCollection.delegate = nil;
    }
    
    [channelCollection remove];
}

SBSMChannelCollection 提供了使用委托的事件处理程序。事件处理程序称为 SBSMChannelCollectionDelegate,它在事件发生时接收 SBSMChannelEventActionchannels 列表。其中 SBSMChannelEventAction 用于通知通道列表中发生了什么,而 channelSBDGroupChannel 实例的类型。您可以创建一个视图控制器实例,实现事件处理程序并将其添加到集合中。

// swift

// add delegate
import SendBirdSyncManager

class GroupChannelListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SBSMChannelCollectionDelegate {
    override func viewDidLoad() {
        // ...
        channelCollection?.delegate = self
        // ...
    }

    // channel collection delegate
    func collection(_ collection: SBSMChannelCollection, didReceiveEvent action: SBSMChannelEventAction, channels: [SBDGroupChannel]) {
        switch (action) {
        case SBSMChannelEventAction.insert:
            // Insert channels on list
            break
        case SBSMChannelEventAction.update:
            // Update channels of list
            break
        case SBSMChannelEventAction.remove:
            // Remove channels of list
            break
        case SBSMChannelEventAction.move:
            // Move channel of list
            break
        case SBSMChannelEventAction.clear:
            // Clear(Remove all) channels
            break
        case SBSMChannelEventAction.none:
            break
        default:
            break
        }
    }
}
// objective-c

// add delegate
#import <SendBirdSyncManager/SendBirdSyncManager.h>
@interface GroupChannelListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, SBSMChannelCollectionDelegate>
@end

@implementation GroupChannelListViewController
- (void)viewDidLoad {    
    channelCollection.delegate = self;
    // ..
}

// channel collection delegate
- (void)collection:(SBSMChannelCollection *)collection didReceiveEvent:(SBSMChannelEventAction)action channels:(NSArray<SBDGroupChannel *> *)channels {
    guard collection == self.channelCollection, channels.count > 0 else {
        return
    }

    switch (action) {
        case SBSMChannelEventActionInsert: {
            // Insert channels on list
            break;
        }
        case SBSMChannelEventActionUpdate: {
            // Update channels of list
            break;
        }
        case SBSMChannelEventActionRemove: {
            // Remove channels of list
            break;
        }
        case SBSMChannelEventActionMove: {
            // Move channel of list
            break;
        }
        case SBSMChannelEventActionClear: {
            // Clear(Remove all) channels
            break;
        }
        case SBSMChannelEventActionNone:
        default: {
            break;
        }
    }
}

- 数据获取器

获取的通道将传递给委托方法。获取器会自动确定 SBSMChannelEventAction,因此不需要在视图中考虑重复的数据。通常,当视图创建时,或者用户请求通道列表的下一页并希望刷新通道列表时,会调用 fetch(_:)

// swift
override viewDidLoad() {
    channelCollection.fetch(completionHandler: {(error) in
        // This callback is optional and useful to catch the moment of loading ended.
    })
}

func refreshChannel() {
    // begin loading progress
    channelCollection?.remove()
    channelCollection? = nil
    // create channel collection
    channelCollection?.fetch(completionHandler: { (error) in
        // end load progress
    })
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // .. dequeue reusable cell        
    if self.channels.count > 0 && indexPath.row + 1 == self.channels.count {
        channelCollection?.fetch(completionHandler: { (error) in
            // end load progress
        })
    }
    // ...
}
// objective-c
- (void)viewDidLoad {    
    [channelCollection fetchWithCompletionHandler:^(SBDError * _Nullable error) {
        // This callback is optional and useful to catch the moment of loading ended.
    }];
}

- (void)refreshChannel {
    // begin loading progress
    [channelCollection remove];
    channelCollection = nil;
    // create channel collection 
    [channelCollection fetchWithCompletionHandler:^(SBDError * _Nullable error) {
        // end loading progress
    }];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // .. dequeue reusable cell

    if (self.channels.count > 0 && indexPath.row + 1 == self.channels.count) {
        // start loading progress
        [self.channelCollection fetchWithCompletionHandler:^(SBDError * _Nullable error) {
            // end loading progress
        }];
    }

    // ...
}    

- 消息集合

消息是相对静态的数据,SyncManager 支持消息的 全缓存SBSMMessageCollection 执行后台同步,以便将所有消息同步到第一条消息。后台同步 直接影响视图,但它将其存储在本地缓存中。为了更新视图,请显式调用带有方向的 fetch(_:_:),这将从缓存中获取数据并将其发送到收集处理程序。

如果同步完成或同步请求失败,后台同步会停止。

注意:后台同步在后台线程运行。

为了支持各种视图(viewpointTimestamp),SBSMMessageCollection 设置了何时获取消息的时间戳。其中 viewpointTimestamp 是开始前后方向后台同步的时间戳(以及用户首次看到的位置)。这是创建 SBSMMessageCollection 的代码。

通常在消息列表视图控制器的 viewDidLoad() 以及频道集合中创建消息集合。

// swift
override viewDidLoad() {
    // ...
    let filter: SBSMMessageFilter = SBSMMessageFilter.init(messageType: SBDMessageTypeFilter, customType: customTypeFilter, senderUserIds: senderUserIdsFilter)
    let viewpointTimestamp: Int64 = getLastReadTimestamp()
    // or LONG_LONG_MAX if you want to see the most recent messages

    let messageCollection: SBSMMessageCollection? = SBSMMessageCollection.init(channel: channel, filter: filter, viewpointTimestamp: viewpointTimestamp)
    // ...
}
// objective-c
- (void)viewDidLoad {
    // ...
    SBSMMessageFilter *filter = [SBSMMessageFilter filterWithMessageType:SBDMessageTypeFilter customType:customtypeFilter senderUserIds:senderUserIdsFilter];
    long long viewpointTimestamp = getLastReadTimestamp();
    // or LONG_LONG_MAX if you want to see the most recent messages

    SBSMMessageCollection *messageCollection = [SBSMMessageCollection collectionWithChannel:self.channel filter:filter viewpointTimestamp:viewpointTimestamp];
    // ...
}

如果集合过时不再使用,可以取消该集合。建议在消息视图控制器的 deinit 中调用 remove()

// swift
deinit {
    messageCollection?.delegate = nil
    messageCollection?.remove()
}
- (void)dealloc {
    if (self.messageCollection != nil) {
        self.messageCollection.delegate = nil;
    }

    [messageCollection remove];
}

SBSMMessageCollection 为委托提供了事件处理程序,可以将其实现并添加到集合中。事件处理程序名为 SBSMMessageCollectionDelegate,它在事件发生时接收 SBSMMessageEventActionmessages 列表。其中 SBSMMessageEventAction 是一个关键字,用于通知消息中发生了什么,而 messageSendbird SDKSBDBaseMessage 实例。

// swift

// add delegate
import SendBirdSyncManager
class GroupChannelChattingViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SBSMMessageCollectionDelegate {
    override func viewDidLoad() {
        // ...
        messageCollection.delegate = self
        // ...
    }

    // message collection delegate
    func collection(_ collection: SBSMMessageCollection, didReceiveEvent action: SBSMMessageEventAction, messages: [SBDBaseMessage]) {
        guard collection == self.messageCollection, messages.count > 0 else {
            return
        }

        switch action {
        case SBSMMessageEventAction.insert:
            self.chattingView?.insert(messages: messages, completionHandler: nil)
            break
        case SBSMMessageEventAction.update:
            self.chattingView?.update(messages: messages, completionHandler: nil)
            break
        case SBSMMessageEventAction.remove:
            self.chattingView?.remove(messages: messages, completionHandler: nil)
            break
        case SBSMMessageEventAction.clear:
            self.chattingView?.clearAllMessages(completionHandler: nil)
            break
        case SBSMMessageEventAction.none:
            break
        default:
            break
        }
    }
}
// objective-c

// add delegate
#import <SendBirdSyncManager/SendBirdSyncManager.h>
@interface GroupChannelChattingViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, SBSMMessageCollectionDelegate, SBDConnectionDelegate>
@end

@implementation GroupChannelChattingViewController
- (void)viewDidLoad {
    // ..
    messageCollection.delegate = self;
    // ..
}

// message collection delegate
- (void)collection:(SBSMMessageCollection *)collection didReceiveEvent:(SBSMMessageEventAction)action messages:(NSArray<SBDBaseMessage *> *)messages {
    if (self.messageCollection != collection || messages.count == 0) {
        return;
    }

    switch (action) {
        case SBSMMessageEventActionInsert: {
            //
            break;
        }
        case SBSMMessageEventActionUpdate : {
            //
            break;
        }
        case SBSMMessageEventActionRemove: {
            //
            break;
        }
        case SBSMMessageEventActionClear: {
            //
            break;
        }
        case SBSMMessageEventActionNone:
        default:
            break;
    }
}

SBSMMessageCollection 提供了按方向的数据获取器:SBSMMessageDirection.previousSBSMMessageDirection.next。它只从缓存中获取数据,永远不会直接请求 Sendbird 服务器。如果某个方向没有更多数据可用,它会内部等待后台同步,并在同步进度后立即获取同步消息。当视图创建时,通常调用 fetch(_:_:),使用户可以请求消息列表的前一页/下一页,刷新消息列表,并接收重连成功的事件。

注意:如果您的设备存储了足够多的消息,您可以从调用 fetch(_:_:) 方法中获取尽可能多的消息。因此,请确保不要调用比您打算调用的更多次数。我们通过样本项目中的 loading 标志来控制它。

// swift
override func viewDidLoad() {
    messageCollection.fetch(in: SBSMMessageDirection.previous, completionHandler: { (error) in
        // Fetching from cache is done
    })
    messageCollection.fetch(in: SBSMMessageDirection.next, completionHandler: { (error) in
        // Fetching from cache is done
    })
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // .. dequeue reusable cell        
    if self.channels.count > 0 && indexPath.row + 1 == self.channels.count {
        messageCollection?.fetch(in: direction, completionHandler: { (error) in
            // Fetching from cache is done
        })
    }
    // ...
}

func refreshMessages() {
    messageCollection?.resetViewpointTimestamp(getLastReadTimestamp())
    messageCollection?.fetch(in: direction, completionHandler: { (error) in
        // Fetching from cache is done
    })
}

// MARK SendBird Connection Delegate
func didSucceedReconnection() {
    messageCollection?.resetViewpointTimestamp(getLastReadTimestamp())
    messageCollection?.fetch(in: direction, completionHandler: { (error) in
        // Fetching from cache is done
    })
}
// objective-c
- (void)viewDidLoad {
    // ..
    [messageCollection fetchInDirection:SBSMMessageDirectionPrevious completionHandler:^(SBDError * _Nullable error) {
        // Fetching from cache is done
    }];
    [messageCollection fetchInDirection:SBSMMessageDirectionNext completionHandler:^(SBDError * _Nullable error) {
        // Fetching from cache is done
    }];
    // ..
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // .. dequeue reusable cell

    if (self.messages.count > 0 && indexPath.row + 1 == self.messages.count) {
        [messageCollection fetchInDirection:direction completionHandler:^(SBDError * _Nullable error) {
            // fetching from cache is done
        }];
    }

    // ...
}

- (void)refreshMessages {
    [messageCollection resetViewpointTimestamp:getLastReadTimestamp()];
    [messageCollection fetchInDirection:direction completionHandler:^(SBDError * _Nullable error) {
        // Fetching from cache is done
    }];
}

#pragma mark - SendBird Connection Delegate
- (void)didSucceedReconnection {
    [messageCollection resetViewpointTimestamp:getLastReadTimestamp()];
    [messageCollection fetchInDirection:direction completionHandler:^(SBDError * _Nullable error) {
        // Fetching from cache is done
    }];
}

获取的消息将传递给委托。获取器会自动确定 SBSMMessageEventAction,因此不需要在视图中考虑重复的数据。

处理未捕获的消息

SyncManager监听消息事件,例如channel(_:didReceive:)channel(_:didUpdate:),并自动应用变化。但是,如果消息是由currentUser发送的,则不会调用这些方法。你可以在currentUser发送或更新消息时调用相关函数来跟踪消息。SBSMMessageCollection提供将这些消息事件应用于集合的方法。

// swift 

// call collection.appendMessage() after sending message
var previewMessage: SBDUserMessage?
channel.sendUserMessage(with: params, completionHandler: { (theMessage, theError) in
    guard let message: SBDUserMessage = theMessage, let _: SBDError = theError else {
        // delete preview message if sending message fails
        messageCollection.deleteMessage(previewMessage)
        return
    }
    
    messageCollection.appendMessage(message)
})

if let thePreviewMessage: SBDUserMessage = previewMessage {
    messageCollection.appendMessage(thePreviewMessage)
}


// call collection.updateMessage() after updating message
channel.sendUserMessage(with: params, completionHandler: { (theMessage, error) in
    guard let message: SBDUserMessage = theMessage, let _: SBDError = error else {
        return
    }
    
    messageCollection.updateMessage(message)
})
// objective-c 

// call [collection appendMessage:] after sending message
__block SBDUserMessage *previewMessage = [channel sendUserMessageWithParams:params completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        [messageCollection deleteMessage:previewMessage];
        return;
    }
    
    [self.messageCollection appendMessage:userMessage];
}];

if (previewMessage.requestId != nil) {
    [messageCollection appendMessage:previewMessage];
}


// call [collection updateMessage:] after updating message
[channel sendUserMessageWithParams:params completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {    
    [self.messageCollection updateMessage:userMessage];
}];

它只适用于由currentUser发送的消息,这意味着消息发送者应该是currentUser

连接生命周期

你应该在连接到Sendbird服务器后启动SyncManager的同步。在连接上调用resumeSynchronization(),在断开连接时调用pauseSynchronization()。以下是代码示例:

// swift
let manager: SBSMSyncManager = SBSMSyncManager()
manager.resumeSynchronize()

let manager: SBSMSyncManager = SBSMSyncManager()
manager.pauseSynchronize()
// objective-c
SBSMSyncManager *manager = [SBSMSyncManager manager];
[manager resumeSynchronize];

SBSMSyncManager *manager = [SBSMSyncManager manager];
[manager pauseSynchronize];

下面示例显示了连接状态和恢复同步的关系。

// swift

// Request Connect to Sendbird
SBDMain.connect(withUserId: userId) { (user, error) in
    if let theError: NSError = error {
        return
    }
    
    let manager: SBSMSyncManager = SBSMSyncManager()
    manager.resumeSynchronize()
}

// Sendbird Connection Delegate
func didSucceedReconnection() {
    let manager: SBSMSyncManager = SBSMSyncManager()
    manager.resumeSynchronize()
}
// objective-c

// Request Connect to Sendbird
[SBDMain connectWithUserId:userId completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    if (error != nil) {
        // 
        return;
    }
    
    SBSMSyncManager *manager = [SBSMSyncManager manager];
    [manager resumeSynchronize];
}];

// Sendbird Connection Delegate
- (void)didSucceedReconnection {
    SBSMSyncManager *manager = [SBSMSyncManager manager];
    [manager resumeSynchronize];
}

明确执行disconnect()后,你应该选择一个操作。您可以清除当前用户的数据库或停止同步。

// swift
SBDMain.disconnect {

    // clear cache
    SBSMSyncManager().clearCache()

    // stop synchronizing
    SBSMSyncManager().pauseSynchronize()
}
// objective-c
[SBDMain disconnectWithCompletionHandler:^{

    // clear cache
    [[SBSMSyncManager manager] clearCache];

    // stop synchronizing
    [[SBSMSyncManager manager] pauseSynchronize];
}];

警告!不要调用SBDMain.removeAllChannelDelegates()。它不仅删除了你添加的处理程序,还删除了SyncManager管理的处理程序。