iOS - EaseMob 环信的使用
1、环信 环信使用的是 XMPP 协议,它是在 XMPP 的基础上进行的二次开发,对服务器 Openfire 和客户端进行功能模型的添加和客户端 SDK 的封装。环信的本质还是使用 XMPP,基于 Socket 的网络通信,在网络上传输的数据也是 XML。 开发架构: 前提准备: 注册成为环信开发者。 在开发者后台创建 APP 获取 Key。 下载官方 SDK。 官方 开发文档。 根据官网导入 SDK 和相应依赖,初始化应用。 2、环信集成 环信开发文档
XMPP 是一个基于 Socket 通信的即时通讯的协议,它规范了即时通信在网络上数据的传输格式,比如登录,获取好友列表等等的格式。XMPP 在网络传输的数据是 XML 格式。
开发架构:
iOS 框架:XMPPFramework
服务器:Openfire
数据库:MySQL
1、XMPPFramework 的目录结构如下:
| 目录 | 说明 |
|---|---|
| Authentication | 授权,与授权验证相关,如用户名密码等 |
| Categories | 分类,XMPP 自己写的一些分类,尤其是 NSXMLElement+XMPP 扩展是必备的 |
| Core | 核心,这里是 XMPP 的核心文件目录,我们最主要的目光还是要放在这个目录上 |
| Extensions | 扩展,XMPP 的扩展模块,用于扩展各种协议和各种独立的功能,其下每个子目录都是对应的一个单独的子功能 |
| Utilities | 工具,都是辅助类,我们开发者不用关心这里 |
| Vendor | 第三方库,这个目录是 XMPP 所引用的第三方类库,我们也不用关心这里 |
虽然这里有很多个目录,但是我们在开发中基本只关心 Core 和 Extensions 这两个目录下的类。
在 Core 中:
| 目录 | 说明 |
|---|---|
| XMPPElement | 是一个基类,延展出三个子类 |
| XMPPIQ | 请求,用户登录,用户注册,添加好友等 |
| XMPPMessage | 消息,用来发各种消息等 |
| XMPPPresence | 展现,用户上线下线提示等 |
| XMPPStream | 流,非常常用,大部分类的加载都在写在流的懒加载里 |
在 Extensions 中:
| 目录 | 说明 |
|---|---|
| CoreDataStorage | coreData 存储 |
| Reconnect | 重新连接 |
| Roster | 好友管理 |
| SystemInputActivityMonitor | 系统输入的活动监控 |
在 Vendor 中:
| 文件夹 | 说明 |
|---|---|
| CocoaAsyncSocket | 异步 Socket |
| CocoaLumberjack | ⽇志相关 |
| KissXML | XML 解析 |
2、XMPPFramework 中常用的类:
| 类 | 说明 |
|---|---|
| XMPPStream | XMPP 基础服务类 |
| XMPPRoster | 好友列表类 |
| XMPPUserCoreDataStorageObject | 管理用户的类 |
| XMPPRosterCoreDataStorage | 好友列表(用户账号)在 core data 中的操作类 |
| XMPPvCardCoreDataStorage | 好友名片(昵称,签名,性别,年龄等信息)在 core data 中的操作类 |
| XMPPvCardTemp | 好友名片实体类,从数据库里取出来的都是它 |
| xmppvCardAvatarModule | 好友头像 |
| XMPPReconnect | 如果失去连接,自动重连 |
| XMPPRoom | 提供多用户聊天支持 |
| XMPPPubSub | 发布订阅 |
| XMPPMessageArchiving | 其中有数据表 |
| XMPPMessageArchiving_Message_CoreDataObject | 取出当前信息的类 |
3、XMPPFramework 几个常用到的扩展协议:
| 协议 | 协议简介 |
|---|---|
| XEP-0006 | 使能与网络上某个 XMPP 实体间的通信 |
| XEP-0009 | 在两个 XMPP 实体间传输 XML-RPC 编码请求和响应 |
| XEP-0012 | 最后的活动(判断上线,离开断开) |
| XEP-0045 | 多人聊天相关协议 |
| XEP-0054 | 名片格式的标准文档,个人信息设置 |
| XEP-0060 | 提供通用公共订阅功能 |
| XEP-0065 | 两个 XMPP 用户之间建立一个带外流,主要用于文件传输,sockets5 字节流 |
| XEP-0066 | 二进制数据传输(特殊信息的发送) |
| XEP-0082 | 日期和时间信息的标准化表示 |
| XEP-0085 | 聊天对话中通知用户状态,聊天状态通知 |
| XEP-0100 | 表述了 XMPP 客户端与提供传统的 IM 服务的代理网关之间交换的最佳实践 |
| XEP-0115 | 广播和动态发现客户端、设备、或一般实体能力 |
| XEP-0136 | 为服务端备份和检索 XMPP 消息定义机制和偏好设置,聊天记录归档 |
| XEP-0153 | 用于交换用户头像,基于名片的头像 |
| XEP-0184 | 消息送达回执协议 |
| XEP-0199 | XMPP ping 协议(用来 ping 服务器和 ping 自己) |
| XEP-0202 | 用于交换实体间的本地时间信息 |
| XEP-0203 | 用于延迟发送 |
| XEP-0224 | 引起另一个用户注意的协议 |
| XEP-0335 | JSON 容器(可能以后某些信息传输将用 JSON 格式) |
XMPP 的扩展协议 Jingle 使得其支持语音和视频,目前 iOS 尚不支持。
iOS 发送附件(图片,语音,文档…)时比较麻烦,XMPP 框架没有提供发送附件的功能,需要自己实现。
iOS 发送附件实现方法:
2、通过 http 请求的方式将图片/音频文件上传到服务器,然后将图片/音频文件的下载地址通过 xmpp 消息体发送过去,另外一个客户端下载。
音频文件建议转码为 amr,这种格式的音频文件比较小。
登录需要到账号,而所谓的账号其实就是用户唯一标识符(JID),在 XMPP 中使用 XMPPJID 类来表示。
JID 一般由三部分构成:用户名,域名和资源名,格式为 user@domain/resource,例如:test@example.com/Anthony。对应于 XMPPJID 类中的三个属性 user、domain、resource。
如果没有设置主机名(HOST),则使用 JID 的域名(domain)作为主机名,而端口号是可选的,默认是 5222,一般也没有必要改动它。
我们要与服务器连接,就必须通过 XMPPStream 类了,它提供了很多的 API 和属性设置,通过 socket 来实现的。Verdor 目录包含了 CocoaAsyncSocket 这个非常有名的 socket 编程库。XMPPStream 类还遵守并实现了 GCDAsyncSocketDelegate 代理,用于客户端与服务器交互。
@interface XMPPStream : NSObject <GCDAsyncSocketDelegate> 当我们创建 XMPPStream 对象后,我们需要设置代理,才能回调我们的代理方法,这个是支持 multicast delegate,也就是说对于一个 XMPPStream 对象,可以设置多个代理对象,其中协议是XMPPStreamDelegate。
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; 而当我们不希望某个 XMPPStream 对象继续接收到代理回调时,我们通过这样的方式来移除代理。
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- (void)removeDelegate:(id)delegate; 接下来,我们要设置主机和端口,通过设置这两个属性。
// 主机,可选设置,如果没有设置默认会使用 domain
@property (readwrite, copy) NSString *hostName;
// 端口号,默认为 5222
@property (readwrite, assign) UInt16 hostPort; XMPPStream 有 XMPPJID 类对象作为属性,标识用户,因为我们后续很多操作都需要到 myJID。
@property (readwrite, copy) XMPPJID *myJID; 而管理用户在线状态的就交由 XMPPPresence 类了,它同样被作为 XMPPStream 的属性,组合到 XMPPStream 中,后续很多关于用户的操作是需要到处理用户状态的。
@property (strong, readonly) XMPPPresence *myPresence; 这个协议是非常关键的,我们的很多主要操作都集中在这个协议的代理回调上。它分为好几种类型的代理 API,比如授权的、注册的、安全的等。
@protocol XMPPStreamDelegate
@optional
// 将要与服务器连接
- (void)xmppStreamWillConnect:(XMPPStream *)sender;
// 已经与服务器连接,
// 当 TCP Socket 已经与远程主机连接上时会回调此方法
// 若 App 要求在后台运行,需要设置 XMPPStream's enableBackgroundingOnSocket 属性
- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket;
// 当 TCP 与服务器建立连接后会回调此方法
- (void)xmppStreamDidStartNegotiation:(XMPPStream *)sender;
// TLS 传输层协议在将要验证安全设置时会回调
// 参数 settings 会被传到 startTLS,此方法可以不实现的
// 若服务端使用自签名的证书,需要在 settings 中添加 GCDAsyncSocketManuallyEvaluateTrust = YES
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings;
// 上面的方法执行后,下一步就会执行这个代理回调
// 用于在 TCP 握手时手动验证是否受信任
- (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
// 当 stream 通过了 SSL/TLS 的安全验证时,会回调此代理方法
- (void)xmppStreamDidSecure:(XMPPStream *)sender;
// 当 XML 流已经完全打开时(也就是与服务器的连接完成时)会回调此代理方法。此时可以安全地与服务器通信了
- (void)xmppStreamDidConnect:(XMPPStream *)sender;
// 注册新用户成功时的回调
- (void)xmppStreamDidRegister:(XMPPStream *)sender;
// 注册新用户失败时的回调
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error;
// 授权通过时的回调,也就是登录成功的回调
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender;
// 授权失败时的回调,也就是登录失败时的回调
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error;
// 将要绑定 JID resource 时的回调,这是授权程序的标准部分
// 当验证 JID 用户名通过时,下一步就验证 resource。若使用标准绑定处理,return nil 或者不要实现此方法
- (id <XMPPCustomBinding>)xmppStreamWillBind:(XMPPStream *)sender;
// 如果服务器出现 resouce 冲突而导致不允许 resource 选择时,会回调此代理方法
// 返回指定的 resource 或者返回 nil 让服务器自动帮助我们来选择。一般不用实现它
- (NSString *)xmppStream:(XMPPStream *)sender alternativeResourceForConflictingResource:(NSString *)conflictingResource;
// 将要接收 IQ(消息查询)时的回调
- (XMPPIQ *)xmppStream:(XMPPStream *)sender willReceiveIQ:(XMPPIQ *)iq;
// 将要接收到消息时的回调
- (XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message;
// 将要接收到用户在线状态时的回调
- (XMPPPresence *)xmppStream:(XMPPStream *)sender willReceivePresence:(XMPPPresence *)presence;
// 通过实现此代理方法,可以知道被过滤的原因,有一定的帮助
// 当 xmppStream:willReceiveX: (也就是前面这三个 API 回调后),过滤了 stanza,会回调此代理方法
- (void)xmppStreamDidFilterStanza:(XMPPStream *)sender;
// 在接收了 IQ(消息查询后)会回调此代理方法
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq;
// 在接收了消息后会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message;
// 在接收了用户在线状态消息后会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence;
// 在接收 IQ/messag、presence 出错时,会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(NSXMLElement *)error;
// 将要发送 IQ(消息查询时)时会回调此代理方法
- (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq;
// 在将要发送消息时,会回调此代理方法
- (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message;
// 在将要发送用户在线状态信息时,会回调此方法
- (XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence;
// 在发送 IQ(消息查询)成功后会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didSendIQ:(XMPPIQ *)iq;
// 在发送消息成功后,会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message;
// 在发送用户在线状态信息成功后,会回调此方法
- (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence;
// 在发送 IQ(消息查询)失败后会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error;
// 在发送消息失败后,会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error;
// 在发送用户在线状态失败信息后,会回调此方法
- (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error;
// 当修改了 JID 信息时,会回调此代理方法
- (void)xmppStreamDidChangeMyJID:(XMPPStream *)xmppStream;
// 当 Stream 被告知与服务器断开连接时会回调此代理方法
- (void)xmppStreamWasToldToDisconnect:(XMPPStream *)sender;
// 当发送了 </stream:stream> 节点时,会回调此代理方法
- (void)xmppStreamDidSendClosingStreamStanza:(XMPPStream *)sender;
// 连接超时时会回调此代理方法
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender;
// 当与服务器断开连接后,会回调此代理方法
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error;
// P2P 类型相关的
- (void)xmppStream:(XMPPStream *)sender didReceiveP2PFeatures:(NSXMLElement *)streamFeatures;
- (void)xmppStream:(XMPPStream *)sender willSendP2PFeatures:(NSXMLElement *)streamFeatures;
- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module;
- (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module;
// 当发送非 XMPP 元素节点时,会回调此代理方法
// 也就是说,如果发送的 element 不是 <iq>, <message> 或者 <presence>,那么就会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didSendCustomElement:(NSXMLElement *)element;
// 当接收到非 XMPP 元素节点时,会回调此代理方法
// 也就是说,如果接收的element不是 <iq>, <message> 或者 <presence>,那么就会回调此代理方法
- (void)xmppStream:(XMPPStream *)sender didReceiveCustomElement:(NSXMLElement *)element; 消息查询(IQ)就是通过此类来处理的了。XMPP 给我们提供了 IQ 方便创建的类,用于快速生成 XML 数据。
@interface XMPPIQ : XMPPElement
// 生成 IQ
+ (XMPPIQ *)iq;
+ (XMPPIQ *)iqWithType:(NSString *)type;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid;
+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement;
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
// IQ 类型
- (NSString *)type;
// 判断 type 类型
- (BOOL)isGetIQ;
- (BOOL)isSetIQ;
- (BOOL)isResultIQ;
- (BOOL)isErrorIQ;
// 当 type 为 get 或者 set 时,这个 API 是很有用的,用于指定是否要求有响应
- (BOOL)requiresResponse;
- (NSXMLElement *)childElement;
- (NSXMLElement *)childErrorElement;
@end IQ 是一种请求/响应机制,从一个实体发送请求,另外一个实体接受请求并进行响应。例如,Client 在 stream 的上下文中插入一个元素,向 Server 请求得到自己的好友列表,Server 返回一个,里面是请求的结果。
<type></type> 有以下类别(可选设置如:<type>get</type>)
| type | 说明 |
|---|---|
| get | 获取当前域值。类似于 http get 方法 |
| set | 设置或替换 get 查询的值。类似于 http put 方法 |
| result | 说明成功的响应了先前的查询。类似于 http 状态码 200 |
| error | 查询和响应中出现的错误 |
下面是一个 IQ 例子:
<iqfrom="huangyibiao@welcome.com/ios"
id="xxxxxxx"
to="biaoge@welcome.com/ios"
type="get">
<queryxmlns="jabber:iq:roster"/>
</iq> 这个类代表节点,我们通过此类提供的方法来生成 XML 数据。presence 它代表用户在线状态。
@interface XMPPPresence : XMPPElement
// Converts an NSXMLElement to an XMPPPresence element in place (no memory allocations or copying)
+ (XMPPPresence *)presenceFromElement:(NSXMLElement *)element;
+ (XMPPPresence *)presence;
+ (XMPPPresence *)presenceWithType:(NSString *)type;
// type:用户在线状态,to:接收方的 JID
+ (XMPPPresence *)presenceWithType:(NSString *)type to:(XMPPJID *)to;
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)to;
- (NSString *)type;
- (NSString *)show;
- (NSString *)status;
- (int)priority;
- (int)intShow;
- (BOOL)isErrorPresence;
@end presence 用来表明用户的状态,如:online、offline、away、dnd (请勿打扰) 等。当改变自己的状态时,就会在 stream 的上下文中插入一个 Presence 元素,来表明自身的状态。要想接受 presence 消息,必须经过一个叫做 presence subscription 的授权过程。
<type></type> 有以下类别(可选设置如:<type>subscribe</type>):
| type | 说明 |
|---|---|
| available | 上线 |
| unavailable | 下线 |
| away | 离开 |
| do not disturb | 忙碌 |
| subscribe | 订阅其他用户的状态 |
| probe | 请求获取其他用户的状态 |
| unavailable | 不可用,离线(offline)状态 |
<show></show> 节点有以下类别,如 <show>dnd</show> :
| show | 说明 |
|---|---|
| chat | 聊天中 |
| away | 暂时离开 |
| xa | eXtend Away,长时间离开 |
| dnd | 勿打扰 |
<status></status> 节点
<priority></priority> 节点
发送一个用户在线状态的例子:
<presencefrom="alice@wonderland.lit/pda">
<show>dnd</show>
<status>浏览器搜索</status>
</presence> XMPPMessage 是 XMPP 框架给我们提供的,方便用于生成 XML 消息的数据。
@interface XMPPMessage : XMPPElement
// Converts an NSXMLElement to an XMPPMessage element in place (no memory allocations or copying)
+ (XMPPMessage *)messageFromElement:(NSXMLElement *)element;
+ (XMPPMessage *)message;
+ (XMPPMessage *)messageWithType:(NSString *)type;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)to;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid;
+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+ (XMPPMessage *)messageWithType:(NSString *)type child:(NSXMLElement *)childElement;
- (id)init;
- (id)initWithType:(NSString *)type;
- (id)initWithType:(NSString *)type to:(XMPPJID *)to;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
- (NSString *)type;
- (NSString *)subject;
- (NSString *)body;
- (NSString *)bodyForLanguage:(NSString *)language;
- (NSString *)thread;
- (void)addSubject:(NSString *)subject;
- (void)addBody:(NSString *)body;
- (void)addBody:(NSString *)body withLanguage:(NSString *)language;
- (void)addThread:(NSString *)thread;
- (BOOL)isChatMessage;
- (BOOL)isChatMessageWithBody;
- (BOOL)isErrorMessage;
- (BOOL)isMessageWithBody;
- (NSError *)errorMessage;
@end message 是一种基本 推送 消息方法,它不要求响应。主要用于 IM、groupChat、alert 和 notification 之类的应用中。
<type></type> 有以下类别(可选设置如:<type>chat</type>):
| type | 说明 |
|---|---|
| normal | 类似于 email,主要特点是不要求响应 |
| chat | 类似于 qq 里的好友即时聊天,主要特点是实时通讯 |
| groupchat | 类似于聊天室里的群聊 |
| headline | 用于发送 alert 和 notification |
| error | 如果发送 message 出错,发现错误的实体会用这个类别来通知发送者出错了 |
<body></body> 节点
消息节点的例子:
<messageto="lily@jabber.org/contact" type="chat">
<body>您好?</body>
</message> 1、通过 CocoaPods 导入第三方框架 XMPPFramework。
在 Podfile 文件中加入如下代码,在终端中,使用命令 pod install 下载添加 XMPPFramework 框架。
platform :ios, '8.0'
target 'XMPPDemo' do
use_frameworks!
pod 'XMPPFramework', '~> 3.7.0'
end 2、在需要使用 XMPPFramework 的文件中导入以下头文件。
#import <XMPPFramework/XMPPFramework.h> 1、用 Cocoapods 集成 XMPPFramework 遇 Module 'KissXML' not found 等问题解决方法。
一般来说,通过 Coacopods 集成集成第三方框架,不会再有依赖库方面的问题,所以需要检查导入方式是否正确,最终找到原因,仔细看 githup 上导入说明
Install
The minimum deployment target is iOS 8.0 / macOS 10.8.
The easiest way to install XMPPFramework is using CocoaPods. Remember to add to the top of your Podfile the
use_frameworks! line (even if you are not using swift): 因此,Podfile 里必须写入这一句
use_frameworks! 2、Xcode8 之后 XMPP 重定义 Redefinition of module 'dnssd' 问题解决方法。
在升级 Xcode 到 8 之后,原来的关于 XMPP 的项目运行报错,错误信息为: Redefinition of module 'dnssd'。系统和XMPP框架同时用到了 'dnssd',大概就是错误的原因。
解决方案:
# The version pushed to CocoaPods is very out of date, use master branch for now
pod 'XMPPFramework', :git => "https://github.com/robbiehanson/XMPPFramework.git", :branch => 'master'
大概意思是需要更新 XMPP 框架,需要把 Podfile 文件中的
pod 'XMPPFramework', '~> 3.6.6'
用
pod 'XMPPFramework', :git => "https://github.com/robbiehanson/XMPPFramework.git", :branch => 'master'
来替换
或者直接改成
pod 'XMPPFramework', '~> 3.7.0' 3、在 pod update 的过程中有的童鞋会遇到下面这样的错误。
这个是因为更新的 XMPP 框架中支持的最低版本为 iOS 8.0 / macOS 10.8。The minimum deployment target is iOS 8.0 / macOS 10.8.
把 Podfile 文件中
platform:ios, '7.0'
4、pod 更新完成了,出现下面这样的错误。
到报错的工程里面搜一下
Enable Strict Checking of objc_msgSend Calls
初始化
/// 包含头文件
#import <XMPPFramework/XMPPFramework.h>
/// 遵守协议
<XMPPStreamDelegate>
/// 定义 XMPP 服务器相关信息
#define HOST_DOMAIN @"jhq0228-macbookair.local"
#define HOST_NAME @"jhq0228-macbookair.local"
#define HOST_PORT 5222
/// 注册的账号
@property (nonatomic, copy) NSString *registerUserName;
/// 注册的密码
@property (nonatomic, copy) NSString *registerPassWord;
/// XMPP 流
@property (nonatomic, strong) XMPPStream *stream;
/// 初始化
self.stream = [[XMPPStream alloc] init];
self.stream.hostName = HOST_NAME;
self.stream.hostPort = HOST_PORT;
[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()]; 与服务器建立链接
/// 与服务器建立链接
[self connectToSercerWithUserName:self.registerUserName resource:nil];
#pragma mark 与服务器连接通信
/// 与服务器建立链接,自定义方法
- (void)connectToSercerWithUserName:(NSString *)userName resource:(NSString *)resource {
if ([self.stream isConnected]) {
[self disconnectWithServer];
}
// jid
self.stream.myJID = [XMPPJID jidWithUser:userName domain:HOST_DOMAIN resource:resource];
NSError *error = nil;
// 进行链接
[self.stream connectWithTimeout:30.0 error:&error];
if (error != nil) {
NSLog(@"连接出现问题");
}
} 进行注册
#pragma mark XMPPStreamDelegate 协议方法
/// 与服务器连接成功
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
NSError *error1 = nil;
// 进行注册
[self.stream registerWithPassword:self.registerPassWord error:&error1];
if (error1 != nil) {
NSLog(@"注册出现问题");
}
}
/// 与服务器连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
NSLog(@"连接服务器超时,请检查网络链接后再试!");
}
/// 注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender {
NSLog(@"注册成功");
}
/// 注册失败
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
NSLog(@"注册失败");
} 初始化
/// 包含头文件
#import <XMPPFramework/XMPPFramework.h>
/// 遵守协议
<XMPPStreamDelegate>
/// 定义 XMPP 服务器相关信息
#define HOST_DOMAIN @"jhq0228-macbookair.local"
#define HOST_NAME @"jhq0228-macbookair.local"
#define HOST_PORT 5222
/// 登录的账号
@property (nonatomic, copy) NSString *loginUserName;
/// 登录的密码
@property (nonatomic, copy) NSString *loginPassWord;
/// XMPP 流
@property (nonatomic, strong) XMPPStream *stream;
/// 初始化
self.stream = [[XMPPStream alloc] init];
self.stream.hostName = HOST_NAME;
self.stream.hostPort = HOST_PORT;
[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()]; 与服务器建立链接
/// 与服务器建立链接
[self connectToSercerWithUserName:self.loginUserName resource:nil];
#pragma mark 与服务器连接通信
/// 与服务器建立链接,自定义方法
- (void)connectToSercerWithUserName:(NSString *)userName resource:(NSString *)resource {
if ([self.stream isConnected]) {
[self disconnectWithServer];
}
// jid
self.stream.myJID = [XMPPJID jidWithUser:userName domain:HOST_DOMAIN resource:resource];
NSError *error = nil;
// 进行连接
[self.stream connectWithTimeout:30.0 error:&error];
if (error != nil) {
NSLog(@"连接出现问题");
}
} 进行登录认证
#pragma mark XMPPStreamDelegate 协议方法
/// 与服务器连接成功
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
NSError *error = nil;
// 进行登录认证
[self.stream authenticateWithPassword:self.loginPassWord error:&error];
if (error != nil) {
NSLog(@"登录认证出现问题");
}
}
/// 与服务器连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
NSLog(@"连接服务器超时,请检查网络链接后再试!");
}
/// 登录成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
NSLog(@"登录成功");
// 设置用户在线状态,如果没有添加,别人给你发的消息服务器默认为离线状态,是不会给你发送的
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
[self.stream sendElement:presence];
}
/// 登录失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error {
NSLog(@"登录失败");
} 与服务器断开链接,用户注销
#pragma mark 与服务器连接通信
/// 与服务器断开链接,用户注销,自定义方法
- (void)disconnectWithServer {
// 断开链接
[self.stream disconnect];
}
#pragma mark XMPPStreamDelegate 协议方法
/// 注销成功
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error {
NSLog(@"注销成功");
// 设置用户下线状态
XMPPPresence *presene = [XMPPPresence presenceWithType:@"unavailable"];
[self.stream sendElement:presene];
} 用户登录信息本地化存储
/// 包含头文件
#import <SAMKeychain/SAMKeychain.h>
/// 用户名和密码
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *userPasswd;
/// 是否记住密码
@property (nonatomic, assign, getter=isSavePasswd) BOOL savePasswd;
/// 保存用户登录信息
- (void)saveUserLoginInfo {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:self.userName forKey:@"userNameKey"];
[userDefaults setBool:self.isSavePasswd forKey:@"isSavePwdKey"];
[userDefaults synchronize];
if (self.isSavePasswd) {
[SAMKeychain setPassword:self.userPasswd forService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
NSLog(@"保存用户登录信息");
} else {
self.userPasswd = nil;
[SAMKeychain deletePasswordForService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
NSLog(@"不保存用户登录信息");
}
}
/// 读取用户登录信息
- (void)loadUserLoginInfo {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
self.userName = [userDefaults objectForKey:@"userNameKey"];
self.savePasswd = [userDefaults boolForKey:@"isSavePwdKey"];
self.userPasswd = [SAMKeychain passwordForService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
} 初始化
/// 遵守协议
<XMPPStreamDelegate, XMPPRosterDelegate, XMPPRosterMemoryStorageDelegate>
/// 好友列表
@property (nonatomic, strong) XMPPRoster *roster;
/// 本地好友存储器
@property (nonatomic, strong) XMPPRosterMemoryStorage *rosterMemoryStorage;
// 添加好友模块
self.rosterMemoryStorage = [[XMPPRosterMemoryStorage alloc] init];
self.roster = [[XMPPRoster alloc] initWithRosterStorage:self.rosterMemoryStorage
dispatchQueue:dispatch_get_global_queue(0, 0)];
[self.roster activate:self.stream]; // 激活
[self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()]; // 设置代理
[self.roster setAutoFetchRoster:YES]; // 设置好友同步策略,XMPP 一旦连接成功,自动同步好友到本地
[self.roster setAutoAcceptKnownPresenceSubscriptionRequests:NO]; // 关掉自动接收好友请求,默认开启自动同意 获取好友列表
// 手动同步好友列表到本地好友存储器
[self.roster fetchRoster];
// 获取好友列表,从本地好友存储器中读取好友信息
NSArray *users = self.rosterMemoryStorage.unsortedUsers;
// 获取好友账号名称
NSString *userName = [user[0] jid].user;
// 获取好友昵称
NSString *userName = [users[0] nickname];
// 获取好友在线状态
BOOL userStatus = [user[0] isOnline];
#pragma mark XMPPRosterDelegate 协议方法
/// 开始同步好友列表到本地
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender withVersion:(NSString *)version {
}
/// 同步到一个好友节点到本地
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item {
}
/// 同步好友列表到本地完成
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender {
} 刷新好友列表
#pragma mark - XMPPRosterMemoryStorageDelegate 协议方法
/// 本地好友存储器发生改变
- (void)xmppRosterDidChange:(XMPPRosterMemoryStorage *)sender {
// 如果设置了自动同步,当服务器的好友列表发生改变时,会自动同步存入本地好友存储器
} 刷新好友状态
#pragma mark XMPPStreamDelegate 协议方法
/// 好友状态改变
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
// 收到对方取消定阅我的消息,对方删除我、对方状态改变
if ([presence.type isEqualToString:@"unsubscribe"]) {
// 从我的本地好友存储器中将对方移除
[self.roster removeUser:presence.from];
}
} 添加好友
/// 添加好友,自定义方法
- (void)addFriendWithUserName:(NSString *)userName remarkName:(NSString *)remarkName {
NSString *jidString = userName;
// 判断有没有域名,如果没有域名,自己添加形成完整的 jid
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
XMPPJID *friendJID = [XMPPJID jidWithString:jidString];
// 添加好友,remarkName 为备注名称
[self.roster addUser:friendJID withNickname:remarkName];
// [self.roster subscribePresenceToUser:friendJID];
} 收到添加好友申请
#pragma mark XMPPRosterDelegate 协议方法
/// 收到添加好友请求
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence {
NSString *name = [NSString stringWithFormat:@"添加 %@ 为好友?", presence.from.user];
// 同意并添加对方为好友,YES 存入本地好友存储器
[self.roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];
// 拒绝添加对方为好友
[self.roster rejectPresenceSubscriptionRequestFrom:presence.from];
} 删除好友
/// 删除好友,自定义方法
- (void)removeFriendWithUserName:(NSString *)userName {
NSString *jidString = userName;
// 判断有没有域名,如果没有域名,自己添加形成完整的 jid
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
XMPPJID *friendJID = [XMPPJID jidWithString:jidString];
// 删除好友
[self.roster removeUser:friendJID];
} 初始化
/// 遵守协议
<XMPPStreamDelegate>
/// 定义 XMPP 服务器相关信息
#define HOST_DOMAIN @"jhq0228-macbookair.local" 发送文本消息
/// 发送文本消息,自定义方法
- (void)sendMessage:(NSString *)message toUser:(NSString *)userName {
// 消息结构
/*
<message type="chat" to="xiaoming@example.com">
<body>Hello World</body>
</message>
*/
NSString *jidString = userName; // 设置消息接收者
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
// 构建消息
NSXMLElement *msg = [NSXMLElement elementWithName:@"message"];
[msg addAttributeWithName:@"type" stringValue:@"chat"];
[msg addAttributeWithName:@"to" stringValue:jidString];
NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
[body setStringValue:message]; // 设置文本消息内容
[msg addChild:body];
// 发送
[self.stream sendElement:msg];
} 接收文本消息
#pragma mark XMPPStreamDelegate 协议方法
/// 接收到消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
NSString *msg = [[message elementForName:@"body"] stringValue];
} 消息回执
这个是 XEP-0184 协议的内容。
发送消息时附加回执请求
// 消息结构
/*
<message
from="northumberland@shakespeare.lit/westminster"
id="richars2-4.1.247"
to="kingrichard@royalty.england.lit/throne">
<body>Hello World</body>
<request xmlns="urn:xmpp:receipts"/>
</message>
*/
NSString *jidString = userName; // 设置消息接收者
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
// 构建消息
NSString *siID = [XMPPStream generateUUID];
XMPPJID *jid = [XMPPJID jidWithString:jidString];
XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid elementID:siID];
NSXMLElement *receipt = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
[msg addChild:receipt]; // 设置消息回执
[msg addBody:message]; // 设置消息内容
// 发送
[self.stream sendElement:msg]; 收到回执请求的消息,发送回执
/// 接收到消息,XMPPStreamDelegate 协议方法
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
// 消息结构
/*
<message
from="kingrichard@royalty.england.lit/throne"
id="bi29sg183b4v"
to="northumberland@shakespeare.lit/westminster">
<received xmlns="urn:xmpp:receipts" id="richars2-4.1.247">
</message>
*/
// 回执判断
NSXMLElement *request = [message elementForName:@"request"];
if (request) {
// 消息回执
if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"]) {
// 组装消息回执
XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"]
to:message.from
elementID:[message attributeStringValueForName:@"id"]];
NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
[msg addChild:recieved];
// 发送回执
[self.stream sendElement:msg];
}
} else {
NSXMLElement *received = [message elementForName:@"received"];
if (received) {
// 消息回执
if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"]) {
// 发送成功
NSLog(@"message send success!");
}
}
}
// 消息处理
// ...
} 图片和语音文件发送的基本思路:
选择图片
/// 遵守协议
<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
#pragma mark - UIImagePickerControllerDelegate 代理方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *image = info[UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(image);
// 发送图片消息,自定义方法
[[XMPPManager defaultManager] sendMessage:imageData msgType:@"image" toUser:self.userName];
[self dismissViewControllerAnimated:YES completion:nil];
} 发送图片消息
// 发送图片消息
[[XMPPManager defaultManager] sendMessage:imageData msgType:@"image" toUser:self.userName];
/// 发送图片/音频消息,自定义方法
- (void)sendMessage:(NSData *)msgData msgType:(NSString *)type toUser:(NSString *)userName {
NSString *jidString = userName; // 设置消息接收者
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
XMPPJID *jid = [XMPPJID jidWithString:jidString];
XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid];
[msg addBody:type];
// 转换成 base64 的编码
NSString *base64str = [msgData base64EncodedStringWithOptions:0];
// 设置节点内容
XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str];
// 包含子节点
[msg addChild:attachment];
// 发送消息
[self.stream sendElement:msg];
} 接收图片消息
#pragma mark XMPPStreamDelegate 协议方法
/// 接收到消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
if ([message.body isEqualToString:@"image"]) {
for (XMPPElement *node in message.children) {
// 取出消息的解码
NSString *base64str = node.stringValue;
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64str options:0];
UIImage *image = [[UIImage alloc] initWithData:data];
}
}
} 图片和语音文件发送的基本思路:
录制/播放语音
/// 包含头文件
#import <AVFoundation/AVFoundation.h>
/// 录音器
@property(nonatomic, strong) AVAudioRecorder *recorder;
/// 录音时长
@property(nonatomic, assign) NSTimeInterval recordTime;
/// 录音地址
@property(nonatomic, strong) NSURL *recordURL;
/// 播放器
@property(nonatomic, strong) AVAudioPlayer *player;
/// 开始录音
// 自定义方法
- (IBAction)startRecord:(UIButton *)sender {
// 创建录音文件保存路径
NSString *urlStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
self.recordURL = [NSURL URLWithString:[urlStr stringByAppendingPathComponent:@"myRecord.caf"]];
// 创建录音格式设置
NSMutableDictionary *dicM = [NSMutableDictionary dictionary];
[dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey]; // 设置录音格式
[dicM setObject:@(8000) forKey:AVSampleRateKey]; // 设置录音采样率,8000 是电话采样率,对于一般录音已经够了
[dicM setObject:@(1) forKey:AVNumberOfChannelsKey]; // 设置通道,这里采用单声道
[dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey]; // 每个采样点位数,分为 8、16、24、32
[dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey]; // 是否使用浮点数采样
NSDictionary *setting = [dicM copy];
// 创建录音机
self.recorder = [[AVAudioRecorder alloc] initWithURL:self.recordURL settings:setting error:NULL];
// 开始录音
[self.recorder record];
}
/// 停止录音
// 自定义方法
- (IBAction)stopRecord:(UIButton *)sender {
NSTimeInterval time = self.recorder.currentTime;
[self.recorder stop];
if (time < 1.5) {
NSLog(@"时间太短");
} else {
NSLog(@"录音完成");
}
}
/// 播放录音
// 自定义方法
- (void)playAudioData:(NSData *)data {
self.player = [[AVAudioPlayer alloc] initWithData:data error:NULL];
self.player.numberOfLoops = 0;
[self.player prepareToPlay];
[self.player play];
} 发送语音消息
// 发送语音消息
NSData *audioData = [NSData dataWithContentsOfURL:self.recordURL];
NSString *type = [NSString stringWithFormat:@"audio:%.1f秒", self.recordTime];
[[XMPPManager defaultManager] sendMessage:audioData msgType:type toUser:self.userName];
/// 发送图片/音频消息,自定义方法
- (void)sendMessage:(NSData *)msgData msgType:(NSString *)type toUser:(NSString *)userName {
NSString *jidString = userName; // 设置消息接收者
NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
if (![jidString containsString:domainString]) {
jidString = [jidString stringByAppendingString:domainString];
}
XMPPJID *jid = [XMPPJID jidWithString:jidString];
XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid];
[msg addBody:type];
// 转换成 base64 的编码
NSString *base64str = [msgData base64EncodedStringWithOptions:0];
// 设置节点内容
XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str];
// 包含子节点
[msg addChild:attachment];
// 发送消息
[self.stream sendElement:msg];
} 接收语音消息
#pragma mark XMPPStreamDelegate 协议方法
/// 接收到消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
if ([message.body hasPrefix:@"audio"]) {
for (XMPPElement *node in message.children) {
// 取出消息的解码
NSString *base64str = node.stringValue;
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64str options:0];
}
}
} 为了监听服务器是否有效,增加心跳监听,用 XEP-0199 协议。
在 XMPPFrameWork 框架下,封装了 XMPPAutoPing 和 XMPPPing 两个类都可以使用,因为 XMPPAutoPing 已经组合进了 XMPPPing 类,所以 XMPPAutoPing 使用起来更方便。
初始化并启动 ping
/// 包含头文件
#import <XMPPFramework/XMPPFramework.h>
/// 遵守协议
<XMPPAutoPingDelegate>>
/// 心跳检测
@property (nonatomic, strong) XMPPAutoPing *autoPing;
// 添加心跳检测模块
self.autoPing = [[XMPPAutoPing alloc] init]; // 发送的是一个 stream:ping,对方如果想表示自己是活跃的,应该返回一个 pong
[self.autoPing activate:self.stream]; // 激活
[self.autoPing addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.autoPing.pingInterval = 1000; // 定时发送 ping 时间
self.autoPing.respondsToQueries = YES; // 不仅仅是服务器来得响应,如果是普通的用户,一样会响应
self.autoPing.targetJID = [XMPPJID jidWithString:HOST_DOMAIN]; // 设置 ping 目标服务器
// 如果为 nil,则监听 stream 当前连接上的那个服务器
#pragma mark - XMPPAutoPingDelegate 协议方法
/// 已经发送 ping
- (void)xmppAutoPingDidSendPing:(XMPPAutoPing *)sender {
NSLog(@"xmppAutoPingDidSendPing");
}
/// 接收到 pong
- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing *)sender {
NSLog(@"xmppAutoPingDidReceivePong");
}
/// ping 超时
- (void)xmppAutoPingDidTimeout:(XMPPAutoPing *)sender {
NSLog(@"xmppAutoPingDidTimeout");
} 停止 ping
// 停止 ping
[self.autoPing deactivate];
[self.autoPing removeDelegate:self];
self.autoPing = nil; 当意外与服务器断开连接,自动重新连接上去,并且将上一次的信息自动加上去。
初始化
/// 包含头文件
#import <XMPPFramework/XMPPFramework.h>
/// 遵守协议
<XMPPReconnectDelegate>>
/// 自动重连
@property (nonatomic, strong) XMPPReconnect *reconnect;
// 添加自动重连模块
self.reconnect = [[XMPPReconnect alloc] init];
[self.reconnect activate:self.stream]; // 激活
[self.reconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.reconnect.autoReconnect = YES; // 设置是否自动重新连接
#pragma mark - XMPPReconnectDelegate 协议方法
/// 设置是否自动重新连接
- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {
return YES;
}
/// 意外断开连接
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {
NSLog(@"didDetectAccidentalDisconnect");
}
微信关注我们
转载内容版权归作者及来源网站所有!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。
Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。
Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。