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

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

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

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

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

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

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

1. 在 Xcode 中创建一个 iOS 平台下的 **Single View App**，项目设置如下：
   - **Product Name** 设为 `RtmQuickstart`。
   - **Organization Identifier** 设为 `agora`。
   - **User Interface** 选择 **Storyboard**。
   - **Language** 选择 **Objective-C**。
2. 通过以下任意一种方式获取最新的 RTM Objective-C 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-1]

调用 RTM SDK 的任何 API 之前，需要先初始化一个 `AgoraRtmClientKit` 对象实例。参考如下步骤创建用户界面、初始化一个 `AgoraRtmClientKit` 对象实例。

1. 创建用户界面。实时消息 App 一般会有如下输入框和按钮：
   - 用户名、频道名、消息输入框
   - 登录、登出按钮
   - 订阅、取消订阅频道按钮
   - 发送消息按钮

   本节提供简易的 UI 界面设计以便于验证功能特性，你可以根据实际项目需求进行修改。

   1. 使用 **Code View** 打开 `Main.storyboard` 并将文件内容替换为以下代码：

      ```xml
      <?xml version="1.0" encoding="UTF-8"?>
      <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
          <device id="retina6_1" orientation="portrait" appearance="light"/>
          <dependencies>
              <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
              <capability name="Safe area layout guides" minToolsVersion="9.0"/>
              <capability name="System colors in document resources" minToolsVersion="11.0"/>
              <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
          </dependencies>
          <scenes>
              <!--View Controller-->
              <scene sceneID="tne-QT-ifu">
                  <objects>
                      <viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
                          <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                              <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                              <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                              <subviews>
                                  <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M2K-rz-dlO">
                                      <rect key="frame" x="254" y="103" width="38" height="30"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <state key="normal" title="Login"/>
                                      <connections>
                                          <action selector="Login:" destination="BYZ-38-t0r" eventType="touchUpInside" id="UEU-up-ksL"/>
                                      </connections>
                                  </button>
                                  <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CZ1-G5-AkG">
                                      <rect key="frame" x="326" y="103" width="48" height="30"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <state key="normal" title="Logout"/>
                                      <connections>
                                          <action selector="Logout:" destination="BYZ-38-t0r" eventType="touchUpInside" id="a0d-8h-eyX"/>
                                      </connections>
                                  </button>
                                  <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0px-1e-aMC">
                                      <rect key="frame" x="242" y="172" width="62" height="30"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <state key="normal" title="Subscribe"/>
                                      <connections>
                                          <action selector="SubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="12c-US-VQk"/>
                                      </connections>
                                  </button>
                                  <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SDQ-Zt-rBD">
                                      <rect key="frame" x="307" y="172" width="87" height="30"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <state key="normal" title="Unsubscribe"/>
                                      <connections>
                                          <action selector="UnsubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="u4T-ze-wtj"/>
                                      </connections>
                                  </button>
                                  <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="User ID" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="69v-UU-ObH">
                                      <rect key="frame" x="40" y="99" width="187" height="34"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                      <textInputTraits key="textInputTraits"/>
                                  </textField>
                                  <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Channel name" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="dGk-g2-qHK">
                                      <rect key="frame" x="40" y="168" width="187" height="34"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                      <textInputTraits key="textInputTraits"/>
                                  </textField>
                                  <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qqk-6X-Tcb">
                                      <rect key="frame" x="304" y="247" width="88" height="30"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <state key="normal" title="Publish MSG"/>
                                      <connections>
                                          <action selector="SendMessageToMessageChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="XcX-nz-fPl"/>
                                      </connections>
                                  </button>
                                  <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Message content" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="lKg-ap-M6p">
                                      <rect key="frame" x="40" y="245" width="252" height="34"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                      <textInputTraits key="textInputTraits"/>
                                  </textField>
                                  <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-i8-spC">
                                      <rect key="frame" x="40" y="416" width="330" height="428"/>
                                      <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                      <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                      <color key="textColor" systemColor="labelColor"/>
                                      <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                      <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                                  </textView>
                              </subviews>
                              <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                              <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                          </view>
                          <connections>
                              <outlet property="ChannelIDTextField" destination="dGk-g2-qHK" id="Iuh-ow-1MH"/>
                              <outlet property="GroupMsgButton" destination="Qqk-6X-Tcb" id="TV4-sg-bY7"/>
                              <outlet property="GroupMsgTextField" destination="lKg-ap-M6p" id="2Me-Z0-QIH"/>
                              <outlet property="LoginButton" destination="M2K-rz-dlO" id="ZxJ-oV-UKy"/>
                              <outlet property="LogoutButton" destination="CZ1-G5-AkG" id="ZQE-B7-yya"/>
                              <outlet property="MsgTextView" destination="e1v-i8-spC" id="76J-8Q-kKp"/>
                              <outlet property="SubsctibeButton" destination="0px-1e-aMC" id="9TL-8M-l6w"/>
                              <outlet property="UnSubscribeButton" destination="SDQ-Zt-rBD" id="tgd-fK-EfO"/>
                              <outlet property="UserIDTextField" destination="69v-UU-ObH" id="3BH-ci-t7A"/>
                          </connections>
                      </viewController>
                      <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
                  </objects>
                  <point key="canvasLocation" x="131.8840579710145" y="97.767857142857139"/>
              </scene>
          </scenes>
          <resources>
              <systemColor name="labelColor">
                  <color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
              </systemColor>
              <systemColor name="systemBackgroundColor">
                  <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
              </systemColor>
          </resources>
      </document>
      ```

   2. 打开 `ViewController.h` 文件，将文件内容替换为如下代码：

      ```objc
      #import <UIKit/UIKit.h>

      #import <AgoraRtmKit/AgoraRtmKit.h>

      @interface ViewController : UIViewController

      // Buttons
      @property (weak, nonatomic) IBOutlet UIButton *LoginButton;
      @property (weak, nonatomic) IBOutlet UIButton *LogoutButton;
      @property (weak, nonatomic) IBOutlet UIButton *SubsctibeButton;
      @property (weak, nonatomic) IBOutlet UIButton *UnSubscribeButton;
      @property (weak, nonatomic) IBOutlet UIButton *GroupMsgButton;

      // Textfields
      @property (weak, nonatomic) IBOutlet UITextField *UserIDTextField;
      @property (weak, nonatomic) IBOutlet UITextField *ChannelIDTextField;
      @property (weak, nonatomic) IBOutlet UITextField *GroupMsgTextField;

      @property (weak, nonatomic) IBOutlet UITextView *MsgTextView;

      @end
      ```

2. 打开 `ViewController.m` 文件，将文件内容替换为如下代码。你会发现此程序并不完整，不用紧张，我们在后续步骤中将一步步指导你补充完善代码。

   :::info[信息]
   你需要将示例中的 `your_appid` 和 `your_token` 字段替换成你项目的 App ID 和 Token。在测试阶段，为快速验证功能，我们建议你在创建声网项目时将鉴权模式设置为调试模式，然后将示例中的 `your_appid` 和 `your_token` 字段都替换成你项目的 App ID。
   :::

   ```objc
   #import "ViewController.h"

   @interface ViewController ()<AgoraRtmClientDelegate>

   @property(nonatomic, strong)AgoraRtmClientKit* kit;

   @property NSString* appID;
   @property NSString* token;

   @property NSString* uid;
   @property NSString* peerID;
   @property NSString* channelID;
   @property NSString* peerMsg;
   @property NSString* channelMsg;

   @property NSString* text;
   @property NSMutableArray* textArray;

   - (void)AddMsgToRecord:(NSString*)text;

   @end

   @implementation ViewController

   - (void)viewDidLoad {
   [super viewDidLoad];
   // Enter your App ID
   self.appID = @"your_appid";
   self.MsgTextView.textColor = UIColor.blueColor;
   self.textArray = [[NSMutableArray alloc]init];
   }

   // Add the event listener

   // Add message to the UI TextView
   - (void)AddMsgToRecord:(NSString*)text {


   [self.textArray addObject:(self.text)];
   self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"\n")];

   }

   - (IBAction)Login:(id)sender {
       self.uid = self.UserIDTextField.text;
       // Enter your token
       self.token = @"your_token";

       AgoraRtmClientConfig*  rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];

       NSError* initError = nil;
       _kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
       if (initError != nil) {
           self.text = [NSString stringWithFormat:@"init error %@",initError];
           NSLog(@"%@", self.text);
           [self AddMsgToRecord:(self.text)];
       }
       // Log in the RTM server
   }

   - (IBAction)Logout:(id)sender {
   if (_kit != nil) {
       // Log out from the RTM server
       }
   }

   - (IBAction)SubscribeChannel:(id)sender {
   self.channelID = self.ChannelIDTextField.text;
   if (_kit != nil) {
       // Subscribe to a channel
       }
   }

   - (IBAction)UnsubscribeChannel:(id)sender {
       if (_kit == nil)
           return;
       // Unsubscribe from a channel
   }

   - (IBAction)SendMessageToMessageChannel:(id)sender {
   self.channelID = self.ChannelIDTextField.text;
   self.channelMsg = self.GroupMsgTextField.text;
       // Publish a message
   }
   @end
   ```

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

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

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

```objc showLineNumbers
// Paste the following code snippet below "Add the event listener" comment
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
    NSLog(@"%@", self.text);
    self.text = [NSString stringWithFormat:@"receive message\nFrom channel: %@\npublisher:%@\nmessage:%@\n",event.channelName,event.publisher, event.message.stringData];
    [self AddMsgToRecord:(self.text)];
}
```

### 4. 登录服务 [#4-登录服务-1]

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

```objc showLineNumbers
// Paste the following code snippet below "Log in the RTM server" comment
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
    if (errorInfo.errorCode != AgoraRtmErrorOk){
        self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
        NSLog(@"%@", self.text);
    } else {
        NSLog(@"%@", self.text);
        self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
    }
    [self AddMsgToRecord:(self.text)];
}];
```

### 5. 收发消息 [#5-收发消息-1]

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

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

```objc showLineNumbers
// Paste the following code snippet below "Publish a message" comment
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
    if (errorInfo == nil) {
        self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
    } else {
        self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
    }

    [self AddMsgToRecord:(self.text)];
}];
```

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

```objc showLineNumbers
// Paste the following code snippet below "Subscribe to a channel" comment
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
    if(errorInfo == nil) {
        self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
        NSLog(@"%@", self.text);
    } else {
        self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
        NSLog(@"%@", self.text);
    }

    [self AddMsgToRecord:(self.text)];
}];
```

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

``` objc showLineNumbers
// Paste the following code snippet below "Unsubscribe from a channel" comment
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
    if (errorInfo == nil){
        self.text = [NSString stringWithFormat:@"Leave channel successful"];
    } else {
        self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode];
    }

    [self AddMsgToRecord:(self.text)];
}];
```

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

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

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

```objc showLineNumbers
// Paste the following code snippet below "Log out from the RTM server" comment
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
    if (errorInfo == nil){
        self.text = [NSString stringWithFormat:@"Logout successful"];
        NSLog(@"%@", self.text);
        [_kit destroy];
        _kit = nil;
    } else {
        self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
        NSLog(@"%@", self.text);
    }

    [self AddMsgToRecord:(self.text)];
}];
```

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

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

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

```objc
#import "ViewController.h"

@interface ViewController ()<AgoraRtmClientDelegate>

@property(nonatomic, strong)AgoraRtmClientKit* kit;

@property NSString* appID;
@property NSString* token;

@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;

@property NSString* text;
@property NSMutableArray* textArray;

- (void)AddMsgToRecord:(NSString*)text;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Enter your App ID
self.appID = @"your_appid";
self.MsgTextView.textColor = UIColor.blueColor;
self.textArray = [[NSMutableArray alloc]init];
}

- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
    NSLog(@"%@", self.text);
    self.text = [NSString stringWithFormat:@"receive message\nFrom channel: %@\npublisher:%@\nmessage:%@\n",event.channelName,event.publisher, event.message.stringData];
    [self AddMsgToRecord:(self.text)];
}

// Add message to the UI TextView
- (void)AddMsgToRecord:(NSString*)text {


[self.textArray addObject:(self.text)];
self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"\n")];

}

- (IBAction)Login:(id)sender {
    self.uid = self.UserIDTextField.text;
    // Enter your token
    self.token = @"your_token";

    AgoraRtmClientConfig*  rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];

    NSError* initError = nil;
    _kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
    if (initError != nil) {
        self.text = [NSString stringWithFormat:@"init error %@",initError];
        NSLog(@"%@", self.text);
        [self AddMsgToRecord:(self.text)];
    }
    // Log in the RTM server
    [_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
        if (errorInfo.errorCode != AgoraRtmErrorOk){
            self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
            NSLog(@"%@", self.text);
        } else {
            NSLog(@"%@", self.text);
            self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
        }
        [self AddMsgToRecord:(self.text)];
    }];

}

- (IBAction)Logout:(id)sender {
if (_kit != nil) {
    // Log out from the RTM server
    [_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
        if (errorInfo == nil){
            self.text = [NSString stringWithFormat:@"Logout successful"];
            NSLog(@"%@", self.text);
            [_kit destroy];
            _kit = nil;
        } else {
            self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
            NSLog(@"%@", self.text);
        }

        [self AddMsgToRecord:(self.text)];
    }];
}

- (IBAction)SubscribeChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
if (_kit != nil) {
    // Subscribe to a channel
    [_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
        if(errorInfo == nil) {
            self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
            NSLog(@"%@", self.text);
        } else {
            self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
            NSLog(@"%@", self.text);
        }

        [self AddMsgToRecord:(self.text)];
    }];
    }
}

- (IBAction)UnsubscribeChannel:(id)sender {
    if (_kit == nil)
        return;
    // Unsubscribe from a channel
    [_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
        if (errorInfo == nil){
            self.text = [NSString stringWithFormat:@"Leave channel successful"];
        } else {
            self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode]; }

        [self AddMsgToRecord:(self.text)];
    }];
}

- (IBAction)SendMessageToMessageChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
self.channelMsg = self.GroupMsgTextField.text;
    // Publish a message
    [_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
        if (errorInfo == nil)
        {
            self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];        }
        else
        {
            self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];        }

        [self AddMsgToRecord:(self.text)];
    }];
}
@end
```

现在，你可以开始运行你的程序：

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

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

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

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

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

## 下一步 [#下一步-1]

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














