实现收发消息

  • Swift

更新时间不可用

了解如何快速接入实时消息 RTM,完成环境准备、参数配置、示例运行和核心流程验证。

本文将指导你如何利用 RTM Swift SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。

准备工作

开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境和运行环境是否满足平台支持中的最低版本要求。

构建项目

1. 项目配置

根据以下步骤配置你的项目:

  1. 在 Xcode 中创建一个 iOS 平台下的 Single View App,项目设置如下:
    • Product Name 设为 RtmQuickstart。
    • Organization Identifier 设为 agora。
    • User Interface 选择 Storyboard。
    • Language 选择 Swift。
  2. 通过以下任意一种方式获取最新的 RTM Swift SDK 并导入你的项目。
  1. 点击此处下载最新版本的 Objective-C SDK。
  2. 复制 SDK 包中 /libs 路径下所有的 xcframework 文件至项目路径下。
  3. 打开 Xcode,进入 TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content 菜单。
  4. 点击 + > Add Other… > Add Files 添加所有的 xcframework 文件,并确保添加的动态库 Embed 属性设置为 Embed & Sign。

添加完成后,项目会自动链接所需系统库。

  1. 开始前请确保你已安装 Cocoapods,如尚未安装 Cocoapods,参考 Getting Started with CocoaPods 安装说明。

  2. 在终端里进入项目根目录,并运行 pod init 命令。项目文件夹下会生成一个 Podfile 文本文件。

  3. 打开 Podfile 文件,修改文件为如下内容。注意将 Your App 替换为你的 Target 名称。

    Ruby
    platform :ios, '11.0'
    target 'Your App' do
       # 对于 2.2.8 及之后版本:
       pod 'ShengWang-Rtm', '2.3.0', :subspecs => ['RtmKit']
       # 对于 2.2.7 版本:
       # pod 'ShengwangRtm', 'x.y.z'
       # 对于 2.2.6 及之前版本:
       # pod 'AgoraRtm', 'x.y.z'
       # 如需获取 2.2.0 之前的版本,请将包名修改为 AgoraRtm_iOS
    end
  4. 在终端内运行 pod install 命令安装声网 SDK。成功安装后,Terminal 中会显示 Pod installation complete!。

  5. 成功安装后,项目文件夹下会生成一个后缀为 .xcworkspace 的文件,通过 Xcode 打开该文件进行后续操作。

使用如下链接,通过 SPM (Swift Package Manager) 集成 SDK:

Text
https://github.com/AgoraIO/AgoraRtm_Apple.git

信息

如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。

2. 初始化 RTM

调用 RTM SDK 的任何 API 之前,需要先创建一个 AgoraRtmClientKit 对象实例,再初始化该实例。在 ContentView.swift 文件中,添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。

信息

你需要将示例中的 <#YOUR APPID#> 字段替换成你项目的 App ID。

Swift
import SwiftUI
import Combine
import AgoraRtmKit

struct Message: Identifiable, Equatable {
    let id = UUID()
    let content: String
}

class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate{
    // Initialize the AgoraRtmClientKit instance
    var appid: String = <#YOUR APPID#>
    var rtmKit: AgoraRtmClientKit? = nil
    @Published var username: String = ""
    @Published var message: String = ""
    @Published var channel: String = ""
    @Published var messages: [Message] = []

    // Add the event listener

    // Log in the RTM server

    // Log out from the RTM server

    // Subscribe to a channel

    // Unsubscribe from a channel

    // Publish a message
}

// Set user interface
struct ContentView: View {
    @StateObject private var viewModel = ChatViewModel()

    var body: some View {
        VStack {
            TextField("input user name", text: $viewModel.username)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)
            HStack {
                Button(action: viewModel.login) {
                    Text("login")
                        .font(.title)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }

                Button(action: viewModel.logout) {
                    Text("logout")
                        .font(.title)
                        .padding()
                        .background(Color.red)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }

            TextField("channel name", text: $viewModel.channel)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)
            HStack {
                Button(action: viewModel.subscribe) {
                    Text("subscribe")
                        .font(.title)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }

                Button(action: viewModel.unsubscribe) {
                    Text("unsubscribe")
                        .font(.title)
                        .padding()
                        .background(Color.red)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }

            TextField("input message", text: $viewModel.message)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)

            Button(action: viewModel.sendMessage) {
                Text("send")
                    .font(.title)
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }

            // display log
            ScrollViewReader { scrollProxy in
                            List(viewModel.messages) { message in
                                Text(message.content)
                                    .id(message.id)
                            }
                            .onChange(of: viewModel.messages) { _ in
                                if let lastMessage = viewModel.messages.last {
                                    withAnimation {
                                        scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
                                    }
                                }
                            }
                        }
            .padding()

        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

如需了解更多初始化的信息,查看初始配置。

3. 添加事件监听

事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:

Swift
// Paste the following code snippet below "Add the event listener" comment
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
    addToMessageList(str: "RTM link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
    addToMessageList(str: "Message received.\n channel: \(event.channelName), publisher: \(event.publisher),  message content: \(event.message.stringData!)")
}

4. 登录服务

你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:

Swift
// Paste the following code snippet below "Log in the RTM server" comment
func login() {
    if rtmKit != nil {
        addToMessageList(str: "RTM alreay login! Logout first!")
        return
    }
    let config = AgoraRtmClientConfig(appId: appid, userId: username);
    rtmKit = try! AgoraRtmClientKit(config, delegate: self)
    rtmKit?.login(token, completion: { response, error in
        if error != nil {
            self.addToMessageList(str: "login failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
        } else {
            self.addToMessageList(str: "\(self.username) login succsss")
        }
    })
}

5. 收发消息

调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:

信息

你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。

Swift
// Paste the following code snippet below "Publish a message" comment
func sendMessage() {
    guard !message.isEmpty else { return }
    if (rtmKit != nil) {
        rtmKit?.publish(channelName: channel, message: message, option: nil, completion: { response, error in
            if error != nil {
                self.addToMessageList(str: "publish failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
            } else {
                self.addToMessageList(str: "publish message to channel: \(self.channel) succsss")
            }
        })
    }
    message = ""
}

func addToMessageList(str: String) {
    messages.append(Message(content: str))
}

调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:

Swift
// Paste the following code snippet below "Subscribe to a channel" comment
func subscribe() {
    rtmKit?.subscribe(channelName: channel, option: nil, completion: { response, error in
        if error != nil {
            self.addToMessageList(str: "subscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
        } else {
            self.addToMessageList(str: "susbcribe channel: \(self.channel) succsss")
        }
    })
}

如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道。将以下代码添加到程序中:

Swift
// Paste the following code snippet below "Unsubscribe from a channel" comment
func unsubscribe() {
    rtmKit?.unsubscribe(channel, completion: { response, error in
        if error != nil {
            self.addToMessageList(str: "unsubscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
        } else {
            self.addToMessageList(str: "unsusbcribe channel: \(self.channel) succsss")
        }
    })
}

如需了解更多收发消息的信息,查看消息。

6. 登出服务

当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:

Swift
// Paste the following code snippet below "Log out from the RTM server" comment
func logout() {
    if rtmKit == nil {
        addToMessageList(str: "RTM alreay logout!")
        return
    }
    rtmKit?.logout()
    rtmKit?.destroy()
    rtmKit = nil
    addToMessageList(str: "RTM logout!")
}

信息

本操作会影响你账单中的 PCU 计费项。

7. 组合到一起

经过上述步骤,你程序中的代码应该如下所示:

Swift
import SwiftUI
import Combine
import AgoraRtmKit

struct Message: Identifiable, Equatable {
    let id = UUID()
    let content: String
}

class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate{
    // Initialize the AgoraRtmClientKit instance
    var appid: String = <#YOUR APPID#>
    var rtmKit: AgoraRtmClientKit? = nil
    @Published var username: String = ""
    @Published var message: String = ""
    @Published var channel: String = ""
    @Published var messages: [Message] = []

    // Add the event listener
    func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
        addToMessageList(str: "RTM link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
    }
    func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
        addToMessageList(str: "Message received.\n channel: \(event.channelName), publisher: \(event.publisher),  message content: \(event.message.stringData!)")
    }

    // Log in the RTM server
    func login() {
        if rtmKit != nil {
            addToMessageList(str: "RTM alreay login! Logout first!")
            return
        }
        let config = AgoraRtmClientConfig(appId: appid, userId: username);
        rtmKit = try! AgoraRtmClientKit(config, delegate: self)
        rtmKit?.login(appid, completion: { response, error in
            if error != nil {
                self.addToMessageList(str: "login failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
            } else {
                self.addToMessageList(str: "\(self.username) login succsss")
            }
        })
    }

    // Log out from the RTM server
    func logout() {
        if rtmKit == nil {
            addToMessageList(str: "RTM alreay logout!")
            return
        }
        rtmKit?.logout()
        rtmKit?.destroy()
        rtmKit = nil
        addToMessageList(str: "RTM logout!")
    }

    // Subscribe to a channel
    func subscribe() {
        rtmKit?.subscribe(channelName: channel, option: nil, completion: { response, error in
            if error != nil {
                self.addToMessageList(str: "subscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
            } else {
                self.addToMessageList(str: "susbcribe channel: \(self.channel) succsss")
            }
        })
    }

    // Unsubscribe from a channel
    func unsubscribe() {
        rtmKit?.unsubscribe(channel, completion: { response, error in
            if error != nil {
                self.addToMessageList(str: "unsubscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
            } else {
                self.addToMessageList(str: "unsusbcribe channel: \(self.channel) succsss")
            }
        })
    }

    // Publish a message
    func sendMessage() {
        guard !message.isEmpty else { return }
        if (rtmKit != nil) {
            rtmKit?.publish(channelName: channel, message: message, option: nil, completion: { response, error in
                if error != nil {
                    self.addToMessageList(str: "publish failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
                } else {
                    self.addToMessageList(str: "publish message to channel: \(self.channel) succsss")
                }
            })
        }
        message = ""
    }

    func addToMessageList(str: String) {
        messages.append(Message(content: str))
    }
}

// Set user interface
struct ContentView: View {
    @StateObject private var viewModel = ChatViewModel()

    var body: some View {
        VStack {
            TextField("input user name", text: $viewModel.username)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)
            HStack {
                Button(action: viewModel.login) {
                    Text("login")
                        .font(.title)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }

                Button(action: viewModel.logout) {
                    Text("logout")
                        .font(.title)
                        .padding()
                        .background(Color.red)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }

            TextField("channel name", text: $viewModel.channel)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)
            HStack {
                Button(action: viewModel.subscribe) {
                    Text("subscribe")
                        .font(.title)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }

                Button(action: viewModel.unsubscribe) {
                    Text("unsubscribe")
                        .font(.title)
                        .padding()
                        .background(Color.red)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }

            TextField("input message", text: $viewModel.message)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .font(.title)

            Button(action: viewModel.sendMessage) {
                Text("send")
                    .font(.title)
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }

            // display log
            ScrollViewReader { scrollProxy in
                            List(viewModel.messages) { message in
                                Text(message.content)
                                    .id(message.id)
                            }
                            .onChange(of: viewModel.messages) { _ in
                                if let lastMessage = viewModel.messages.last {
                                    withAnimation {
                                        scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
                                    }
                                }
                            }
                        }
            .padding()

        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

现在,你可以开始运行你的程序:

  1. 保存项目。
  2. 复制当前项目,使用相同的 appId 和不同的 userId。
  3. 运行上面的两个项目。成功运行后,你可以在两个设备中看到 App 界面。
  4. 将一个设备作为接收端,进行如下操作:
    1. 输入用户名,点击 login。
    2. 输入频道名,点击 subscribe。
  5. 将另一个设备作为发送端,进行如下操作:
    1. 输入不同的用户名,点击 subscribe。
    2. 输入相同的频道名。
    3. 输入消息,点击 send。
  6. 将上面的发送端和设备端互换,重复步骤 4 和 5。

如果两个设备均可收发消息,那么你已经成功集成并正确使用了 RTM 服务。

贯穿始终

相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:

  1. 设置并初始化 RTM 对象。
  2. 添加 didReceiveMessageEvent 事件监听函数。
  3. 调用 login 登录了 RTM 服务。
  4. 调用 subscribe 订阅了一个 Message Channel。
  5. 调用 publish 发送消息。
  6. 调用 unsubscribe 取消订阅一个 Message Channel。
  7. 调用 logout 登出 RTM 系统。

下一步

现在,你已经学会了如何使用 RTM Swift SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。