# 实现收发消息 (/zh-CN/realtime-media/rtm/get-started/quick-start/swift)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

本文将指导你如何利用 RTM Swift SDK 构建简单的应用程序，内容涵盖了集成和开发的基础知识：开通声网账号、获取 SDK、发送消息、接收消息等。
## 准备工作 [#准备工作-7]

开始构建项目前，你需要完成[开通 RTM 服务](/zh-CN/realtime-media/rtm/build/rtm-initialization/enable-service)，并检查你的开发环境和运行环境是否满足[平台支持](/zh-CN/realtime-media/rtm/reference/capabilities-and-compatibility/platform-support)中的最低版本要求。

## 构建项目 [#构建项目-6]

### 1. 项目配置 [#1-项目配置-4]

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

1. 在 Xcode 中创建一个 iOS 平台下的 **Single View App**，项目设置如下：
   - **Product Name** 设为 `RtmQuickstart`。
   - **Organization Identifier** 设为 `agora`。
   - **User Interface** 选择 **Storyboard**。
   - **Language** 选择 **Swift**。
2. 通过以下任意一种方式获取最新的 RTM Swift SDK 并导入你的项目。

<Tabs defaultValue="cdn">
<TabsList>
  <TabsTrigger value="cdn">使用 CDN</TabsTrigger>
  <TabsTrigger value="cocoapods">使用 Cocoapods</TabsTrigger>
  <TabsTrigger value="spm">通过 SPM 集成 SDK</TabsTrigger>
</TabsList>

<TabsContent value="cdn">

1. [点击此处](https://download.shengwang.cn/rtm2/release/RTM_OC_SDK_v2.3.0.zip)下载最新版本的 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**。

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

</TabsContent>

<TabsContent value="cocoapods">

1. 开始前请确保你已安装 Cocoapods，如尚未安装 Cocoapods，参考 [Getting Started with CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started) 安装说明。
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 打开该文件进行后续操作。

</TabsContent>

<TabsContent value="spm">

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

```text
https://github.com/AgoraIO/AgoraRtm_Apple.git
```

</TabsContent>
</Tabs>

   :::info[信息]
   如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK，请参考 [FAQ](/zh-CN/reference/faq/integration/rtm2_rtc_integration_issue) 处理集成问题。
   :::

### 2. 初始化 RTM [#2-初始化-rtm-3]

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

:::info[信息]
你需要将示例中的 `<#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()
    }
}
```

如需了解更多初始化的信息，查看[初始配置](/zh-CN/api-reference/rtm/swift/configuration)。

### 3. 添加事件监听 [#3-添加事件监听-5]

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

```swift showLineNumbers
// 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. 登录服务 [#4-登录服务-5]

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

```swift showLineNumbers
// 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. 收发消息 [#5-收发消息-5]

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

:::info[信息]
你需要先对消息负载进行字符串序列化，才能调用 `publish` 方法发送消息。
:::

```swift showLineNumbers
// 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 showLineNumbers
// 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 showLineNumbers
// 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")
        }
    })
}
```

如需了解更多收发消息的信息，查看[消息](/zh-CN/api-reference/rtm/swift/message)。

### 6. 登出服务 [#6-登出服务-4]

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

```swift showLineNumbers
// 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!")
}
```

:::info[信息]
本操作会影响你账单中的 PCU 计费项。
:::

### 7. 组合到一起 [#7-组合到一起-4]

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

```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 服务。

## 贯穿始终 [#贯穿始终-7]

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

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

## 下一步 [#下一步-7]

现在，你已经学会了如何使用 RTM Swift SDK 来实现 Message Channel 的收发消息功能。下一步，你可以通过 SDK 的 [API 参考](/zh-CN/api-reference/rtm/swift/configuration)了解更多功能的使用方法。


