登录 RTM 系统包括以下流程:
发送和接收点对点消息包括以下流程:
发送和接收频道消息包括以下流程:
参考以下步骤创建一个声网项目:
声网会给每个项目自动分配一个 App ID 作为项目唯一标识。
在声网控制台的项目管理页面,找到你的项目,点击 App ID 右侧的 图标,即可获取项目的 App ID。
参考以下步骤获取 App 证书:
在声网控制台的项目管理页面,找到你的项目,点击配置。
点击主要证书下面的复制图标,即可获取项目的 App 证书。
为提高项目的安全性,声网推荐使用 Token 对即将登录 RTM 系统的用户进行鉴权。
为了方便测试,声网服务器提供部署签发 RTM Token 的功能。参考以下步骤获取 RTM Token:
login
时,请确保填入的用户 ID 与生成 RTM Token 时填入的用户 ID 一致。参考以下步骤在 Xcode 中创建一个 iOS 平台下的 Single View App,项目设置如下:
RtmQuickstart
。agora
。在 Terminal 里进入项目根目录,并运行 pod init
命令。项目文件夹下会生成一个 Podfile 文本文件。
打开 Podfile 文件,修改文件为如下内容。
# platform :ios, '9.0'
target 'RtmQuickstart' do
use_frameworks!
pod 'AgoraRtm_iOS'
end
运行 pod update
命令更新本地库版本。
运行 pod install
命令安装声网 SDK。成功安装后,Terminal 中会显示 Pod installation complete!
,此时项目文件夹下会生成一个 workspace 文件。
在 Xcode 中,使用文本模式打开 Main.storyboard
并将文件内容替换为以下代码:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" 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="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.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="roundedRect" 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="roundedRect" 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="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0px-1e-aMC">
<rect key="frame" x="258" y="172" width="30" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Join"/>
<connections>
<action selector="JoinChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="iFn-TA-13c"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SDQ-Zt-rBD">
<rect key="frame" x="330" y="172" width="40" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Leave"/>
<connections>
<action selector="LeaveChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Stj-Kn-bNa"/>
</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 ID" 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="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qqk-6X-Tcb">
<rect key="frame" x="308" y="247" width="80" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Group MSG"/>
<connections>
<action selector="SendGroupMsg:" destination="BYZ-38-t0r" eventType="touchUpInside" id="pih-os-loN"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Group 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>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Peer message content" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="zt3-lc-pXB">
<rect key="frame" x="40" y="321" width="170" 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="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kEb-0v-E5p">
<rect key="frame" x="314" y="321" width="69" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Peer MSG"/>
<connections>
<action selector="SendPeerMsg:" destination="BYZ-38-t0r" eventType="touchUpInside" id="YU3-af-mwD"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Peer ID" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Xzo-Ee-qU1">
<rect key="frame" x="218" y="321" width="75" 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" cocoaTouchSystemColor="whiteColor"/>
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="ChannelIDTextField" destination="dGk-g2-qHK" id="Iuh-ow-1MH"/>
<outlet property="GroupMsgButton" destination="Qqk-6X-Tcb" id="vAL-bi-8zT"/>
<outlet property="GroupMsgTextField" destination="lKg-ap-M6p" id="2Me-Z0-QIH"/>
<outlet property="JoinButton" destination="0px-1e-aMC" id="Xdt-56-5nP"/>
<outlet property="LeaveButton" destination="SDQ-Zt-rBD" id="fSm-JU-ZqB"/>
<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="PeerIDTextField" destination="Xzo-Ee-qU1" id="3Rz-GJ-sTN"/>
<outlet property="PeerMsgButton" destination="kEb-0v-E5p" id="1Ik-yZ-b5H"/>
<outlet property="PeerMsgTextField" destination="zt3-lc-pXB" id="V8i-Be-Anc"/>
<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>
</document>
为了帮助你快速实现并理解相关功能,本文通过最简方式,在一个视图里实现以下操作:
ViewController.h
并将内容替换为以下代码://
// ViewController.h
// RtmQuickstart
//
// Created by macoscatalina on 2021/6/8.
// Copyright © 2021 macoscatalina. All rights reserved.
//
#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 *JoinButton;
@property (weak, nonatomic) IBOutlet UIButton *LeaveButton;
@property (weak, nonatomic) IBOutlet UIButton *GroupMsgButton;
@property (weak, nonatomic) IBOutlet UIButton *PeerMsgButton;
// Textfields
@property (weak, nonatomic) IBOutlet UITextField *UserIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *ChannelIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *GroupMsgTextField;
@property (weak, nonatomic) IBOutlet UITextField *PeerMsgTextField;
@property (weak, nonatomic) IBOutlet UITextField *PeerIDTextField;
@property (weak, nonatomic) IBOutlet UITextView *MsgTextView;
@end
ViewController.m
并将内容替换为以下代码。你需要将 Your_App_ID
替换为你的 App ID; 将 Your_Token
替换为你的 Token。//
// ViewController.m
// RtmQuickstart
//
// Created by macoscatalina on 2021/6/8.
// Copyright © 2021 macoscatalina. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()<AgoraRtmChannelDelegate, AgoraRtmDelegate>
@property(nonatomic, strong)AgoraRtmKit* kit;
@property(nonatomic, strong)AgoraRtmChannel* channel;
@property(nonatomic, strong)AgoraRtmSendMessageOptions* options;
@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];
// 你的 App ID
self.appID = @"Your_App_ID";
// 创建 AgoraRtmKit 实例
_kit = [[AgoraRtmKit alloc] initWithAppId:self.appID delegate:self];
self.MsgTextView.textColor = UIColor.blueColor;
self.textArray = [[NSMutableArray alloc]init];
}
// 将消息记录加入 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;
// 你的 Token
self.token = @"Your_Token";
// 登录 RTM
[_kit loginByToken:(self.token) user:(self.uid) completion:^(AgoraRtmLoginErrorCode errorCode) {
if (errorCode != AgoraRtmLoginErrorOk){
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorCode];
NSLog(@"%@", self.text);
}
else {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorCode];
}
[self AddMsgToRecord:(self.text)];
}
];}
// 登出按钮
- (IBAction)Logout:(id)sender {
// 登出 RTM
[_kit logoutWithCompletion:^(AgoraRtmLogoutErrorCode errorCode) {
if (errorCode == AgoraRtmLogoutErrorOk){
self.text = [NSString stringWithFormat:@"Logout successful. Code: %ld",(long)errorCode];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorCode];
NSLog(@"%@", self.text); }
[self AddMsgToRecord:(self.text)];
}];
}
// 加入频道按钮
- (IBAction)JoinChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
// 创建 RTM 频道
_channel = [_kit createChannelWithId:self.channelID delegate:self];
// 加入 RTM 频道
[_channel joinWithCompletion:^(AgoraRtmJoinChannelErrorCode errorCode) {
if(errorCode == AgoraRtmJoinChannelErrorOk){
self.text = [NSString stringWithFormat:@"Successfully joined channel %@ Code: %ld",self.channelID,(long)errorCode];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to join channel %@ Code: %ld",self.channelID, (long)errorCode];
NSLog(@"%@", self.text); }
[self AddMsgToRecord:(self.text)];
}];
}
-(void)channel:(AgoraRtmChannel *)channel memberLeft:(AgoraRtmMember *)member
{
self.text = [NSString stringWithFormat:@"%@ left channel %@", member.channelId, member.userId];
[self AddMsgToRecord:(self.text)];
}
-(void)channel:(AgoraRtmChannel *)channel memberJoined:(AgoraRtmMember *)member
{
self.text = [NSString stringWithFormat:@"%@ joined channel %@", member.channelId, member.userId];
[self AddMsgToRecord:(self.text)];
}
// 离开频道按钮
- (IBAction)LeaveChannel:(id)sender {
[_channel leaveWithCompletion:^(AgoraRtmLeaveChannelErrorCode errorCode) {
if (errorCode == AgoraRtmLeaveChannelErrorOk){
self.text = [NSString stringWithFormat:@"Leave channel successful Code: %ld", (long)errorCode];
} else {
self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorCode]; }
[self AddMsgToRecord:(self.text)];
}];
}
// 发送频道消息按钮
- (IBAction)SendGroupMsg:(id)sender {
self.channelMsg = self.GroupMsgTextField.text;
self.options.enableOfflineMessaging = true;
[_channel sendMessage:[[AgoraRtmMessage alloc] initWithText:self.channelMsg] sendMessageOptions:self.options completion:^(AgoraRtmSendChannelMessageErrorCode errorCode) {
if (errorCode == AgoraRtmSendChannelMessageErrorOk)
{
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)errorCode]; }
[self AddMsgToRecord:(self.text)];
}];
}
// 显示收到的频道消息
- (void)channel:(AgoraRtmChannel *)channel messageReceived:(AgoraRtmMessage *)message fromMember:(AgoraRtmMember *)member
{
self.text = [NSString stringWithFormat:@"Message received in channel: %@ from user: %@ content: %@",member.channelId, member.userId, message.text];
[self AddMsgToRecord:(self.text)];
}
// 发送点对点消息按钮
- (IBAction)SendPeerMsg:(id)sender {
self.peerMsg = self.PeerMsgTextField.text;
self.peerID = self.PeerIDTextField.text;
[_kit sendMessage:[[AgoraRtmMessage alloc] initWithText:self.peerMsg] toPeer:self.peerID completion:^(AgoraRtmSendPeerMessageErrorCode errorCode) {
if (errorCode == AgoraRtmSendPeerMessageErrorOk)
{
self.text = [NSString stringWithFormat:@"Message sent from user: %@ to user: %@ content: %@", self.uid, self.peerID, self.peerMsg];
}
else
{
self.text = [NSString stringWithFormat:@"Message failed to send from user: %@ to user: %@ content: %@ Error: %ld", self.uid, self.peerID, self.peerMsg, (long)errorCode]; }
[self AddMsgToRecord:(self.text)];
}];
}
// 显示收到的点对点消息
- (void)rtmKit:(AgoraRtmKit *)kit messageReceived:(AgoraRtmMessage *)message fromPeer:(NSString*)peerId
{
self.text = [NSString stringWithFormat:@"Message received from user: %@ content: %@", peerId, message.text];
[self AddMsgToRecord:(self.text)];}
// 显示当前用户的连接状态
- (void)rtmKit:(AgoraRtmKit *)kit connectionStateChanged:(AgoraRtmConnectionState)state reason:(AgoraRtmConnectionChangeReason)reason
{
self.text = [NSString stringWithFormat:@"Connection status changed to: %ld Reason: %ld", (long)state, (long)reason];
[self AddMsgToRecord:(self.text)];
}
@end
在运行项目前,你需要设置签名和开发团队,并添加设备权限。
iOS 14.0 版本新增了 Privacy - Local Network Usage Description 权限。如果使用 1.4.1 之前版本的 SDK,你需要添加该权限。详见解决方案。
如使用 1.4.1 及以上版本 SDK,请忽略该步骤。
编译并在模拟器或真机上运行项目。运行效果如下:
选择如下任意一种方式将声网RTM iOS SDK 集成到你的项目中。
pod init
命令。项目文件夹下会生成一个 Podfile 文本文件。Your App
替换为你的 Target 名称。# platform :ios, '9.0'
target 'Your App' do
use_frameworks!
pod 'AgoraRtm_iOS'
end
pod update
命令更新本地库版本。pod install
命令安装声网 SDK。成功安装后,Terminal 中会显示 Pod installation complete!
,此时项目文件夹下会生成一个 workspace 文件。v1.4.4 及以上版本
AgoraRtmKit.xcframework
至项目路径下。添加完成后,项目会自动链接所需系统库。
SDK 使用 XCFramework。如果你使用的第三方工具不支持 XCFramework 集成,声网 RTM 提供如下脚本来帮助你集成:
执行脚本步骤如下:
1 . 请将以上脚本文件拷贝至 AgoraRtmKit.xcframework
所在项目路径下,并执行如下代码生成 AgoraRtmKit.framework
:
sh change_to_all_arch.sh "<AgoraRtmKit.xcframework path>"
sh remove_simulator_arch.sh "<AgoraRtmKit.framework path>"
2 . 打开 Xcode,进入 TARGETS > Project Name > Build Phases > Link Binary with Libraries 菜单,点击 + 添加动态库 AgoraRtmKit.framework
,并确保动态库的 Embed 属性为 Embed & Sign。
v1.4.4 以下版本
将 SDK 包中 libs 文件夹内的 AgoraRtmKit.framework 文件复制到项目文件夹下。
打开 Xcode,进入 TARGETS > Project Name > Build Phases > Link Binary with Libraries 菜单,点击 + 添加如下库。在添加 AgoraRtmKit.framework 文件时,还需在点击 + 后点击 Add Other…,找到本地文件并打开。
RTM 支持多个相互独立的 AgoraRtmKit 实例。
在收发点对点消息或进行其他频道操作前,请确保你已成功登录声网 RTM 系统(即确保已经收到 AgoraRtmLoginErrorOk
)。
使用频道核心功能前必须通过调用 createChannelWithId 方法创建频道实例。
你可以创建多个 AgoraRtmKit 客户端实例,但是每个客户端实例最多只能同时加入 20 个频道。每个频道的 channelId
参数应该不同。
当离开了频道且不再加入该频道时,可以调用 destroyChannelWithId 方法及时释放频道实例所占用的资源。
接收到的 AgoraRtmMessage 消息对象不能重复利用再用于消息发送。