为了方便登录、注册功能的编写,以及为了代码的可读性考虑,新建XMPPTool类,把XMPP框架的使用写成一个工具类。其中注册、注销、登录都会和服务器进行通信,它们的实现步骤如下。
登录的步骤分为4步:
1. 初始化XMPPStream (与服务器交互的核心类)
2. 连接服务器
3. 发送密码
4. 发送在线消息(默认登录成功是不在线的)
注册的步骤分为3步:
1. 初始化XMPPStream
2. 连接服务器
3. 注册(发送注册密码)
注销的步骤分2步:
1. 发送离线消息
2. 断开连接
因此,根据以上对功能的分析,可以得出XMPP的工具类需要有的方法如下:
1. 首先,需要些6个私有方法,完成初始化XMPPStream、连接服务器、发送密码、发送在线消息、发送离线消息、断开连接的功能;
2. 之后,需要有3个公共方法,完成注册、登录、注销的功能,方便之后需要用到时随时调用;
3. 最后,需要实现XMPP的5个代理方法,分别是连接成功、登录成功、登录失败、注册成功、注册失败。
属性方面,因为要告诉用户连接服务器、登录、注册的结果,这个结果返回的方式最好用block;因为注册、登录操作都需要连接服务器,因此应该设置一个BOOL型变量,判断当前操作是登录还是注册;还有域名、端口等属性就不一一细讲了。
新建头文件Singleton
// .h
#define singleton_interface(class) + (instancetype)shared##class;
// .m
#define singleton_implementation(class) \ static class *_instance; \ \ + (id)allocWithZone:(struct _NSZone *)zone \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [super allocWithZone:zone]; \ }); \ \ return _instance; \ } \ \ + (instancetype)shared##class \ { \ if (_instance == nil) { \ _instance = [[class alloc] init]; \ } \ \ return _instance; \ }
其实不是很懂上面这个头文件的写法,只知道导入该文件之后,单例模式的编写就变得很简单,下面会用到。
注册、登录都需要连接服务器,且连接时都需要发送一个JID,JID可以认为是用户的帐号,因此在XMPPTool中需要定义一个属性,来判断当前操作是注册还是登录。同时,需要定义用户注册、登录的公共方法,并返回结果,返回结果的方法应使用block。且XMPPTool应该是单例的,这里定义单例的方法是使用上面那个类。
typedef enum{
XMPPResultTypeLoginSuccess,
XMPPResultTypeLoginFailure,
XMPPResultTypeRegisterSuccess,
XMPPResultTypeRegisterFailure
}XMPPResultType;
typedef void (^XMPPResultBlock)(XMPPResultType);
@interface WCXMPPTool : NSObject
singleton_interface(WCXMPPTool) //单例
//与服务器交互的核心类
@property (nonatomic, strong, readonly) XMPPStream *xmppStream;
/** * 判断执行的登录还是注册操作, YES为注册, NO为登录 */
@property (nonatomic, assign, getter=isRegisterOperation) BOOL registerOperation;
/** * 用户登录 */
- (void)xmppLogin:(XMPPResultBlock)resultBlock;
/** * 用户注销 */
- (void)xmppLogout;
/** * 用户注册 */
- (void)xmppRegister:(XMPPResultBlock)resultBlock;
/** * 服务器域名 */
@property (nonatomic, copy, readonly) NSString *domain;
/** * 服务器IP */
@property (nonatomic, copy, readonly) NSString *host;
@property (nonatomic, assign) int port;
@end
@interface WCXMPPTool () <XMPPStreamDelegate>
{
//结果回调block
XMPPResultBlock _resultBlock;
}
/** * 1.初始化服务器 */
- (void)setupStream;
/** * 2.连接服务器 */
- (void)connectToHost;
/** * 3.发送密码 */
- (void)sendPwdToHost;
/** * 4.发送在线信息 */
- (void)sendOnline;
/** * 5.发送离线消息 */
- (void)sendOffLine;
/** * 6.断开连接 */
- (void)disconnectFromHost;
@end
@implementation WCXMPPTool
singleton_implementation(WCXMPPTool)
#pragma mark 私有方法
- (void)setupStream
{
_xmppStream = [[XMPPStream alloc] init];
//所有代理方法都在子线程中执行
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
- (void)connectToHost
{
if (!_xmppStream) {
[self setupStream];
}
//设置用户登录的JID,resource为用户登录客户端的设备类型
XMPPJID *myJid = nil;
WCUser *user = [WCUser shareUser];
if (self.isRegisterOperation) {
NSString *registerAccount = [WCUser shareUser].registerAccount;
myJid = [XMPPJID jidWithUser:registerAccount domain:user.domain resource:@"iphone"];
}else {
NSString *loginAccount = [WCUser shareUser].loginAccount;
myJid = [XMPPJID jidWithUser:loginAccount domain:user.domain resource:@"iphone"];
}
_xmppStream.myJID = myJid;
//设置主机地址
_xmppStream.hostName = user.host;
//设置主机端口号,可以不设置,默认为5222
_xmppStream.hostPort = user.port;
//发起连接,缺少必要参数就会连接失败(比如没有JID)
NSError *error = nil;
[_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error];
if (!error) {
ZHLog(@"连接成功");
}else {
ZHLog(@"连接失败,%@",error);
}
}
- (void)sendPwdToHost
{
NSError *error = nil;
NSString *loginPwd = [WCUser shareUser].loginPwd;
[_xmppStream authenticateWithPassword:loginPwd error:&error];
if (!error) {
ZHLog(@"登录成功");
}else {
ZHLog(@"登录失败,%@", error);
}
}
- (void)sendOnline { XMPPPresence *presence = [XMPPPresence presence]; ZHLog(@"%@",presence); [_xmppStream sendElement:presence]; }
- (void)sendOffLine
{
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
[_xmppStream sendElement:presence];
}
- (void)disconnectFromHost {
[_xmppStream disconnect];
}
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
ZHLog(@"%s",__func__);
if (self.isRegisterOperation) {
//注册
NSError *error = nil;
NSString *registerPwd = [WCUser shareUser].registerPwd;
[_xmppStream registerWithPassword:registerPwd error:&error];
if (error) {
ZHLog(@"error:%@", error);
}
}else {
[self sendPwdToHost];
}
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
[self sendOnline];
if (_resultBlock) {
_resultBlock(XMPPResultTypeLoginSuccess);
//实现登录功能的时候,登录成功之后需要销毁登录控制器
//因此block回掉成功的信息后,把block设置nil,不让登录控制器继续持有block,可以让登录控制器自动销毁
//_resultBlock = nil;
}
}
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error { ZHLog(@"error: %@",error); if (_resultBlock) { _resultBlock(XMPPResultTypeLoginFailure); } }
- (void)xmppStreamDidRegister:(XMPPStream *)sender { if (_resultBlock) { _resultBlock(XMPPResultTypeRegisterSuccess); } }
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error { if (_resultBlock) { _resultBlock(XMPPResultTypeRegisterFailure); } }
- (void)xmppLogin:(XMPPResultBlock)resultBlock
{
//把以前的登录断开,不然如果先输错密码登录失败,之后再登录不管密码是对是错,都会登录失败
//因为连接服务器和发送密码有先后顺序,之前即使输错了密码,实际上还是成功了连接了服务器
//不把它断开,会报错——试图连接一个已存在的链接
[_xmppStream disconnect];
_resultBlock = resultBlock;
[self connectToHost];
}
- (void)xmppLogout
{
[self sendOffLine];
[self disconnectFromHost];
}
- (void)xmppRegister:(XMPPResultBlock)resultBlock { [_xmppStream disconnect]; _resultBlock = resultBlock; [self connectToHost]; }
可以看到,上面的代码中,打印消息的时候用的是ZHLog,而不是NSLog,是因为在pch文件中定义了自己的打印消息的方法:
/** * 自定义Log */
#ifdef DEBUG
//#define ZHLog(...) NSLog(__VA_ARGS__)
#define ZHLog(...) NSLog(@"%s\n %@\n\n", __func__, [NSString stringWithFormat:__VA_ARGS__]);
#else
#define ZHLog(...)
#endif
#endif
判断用户之前是否登录成功,如果登录成功,则不需重复登录,直接跳转到Main.storyboard的功能应该在AppDelegate中编写。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
//判断用户是否登录
if ([WCUser shareUser].isLogin) {
id mainVc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateInitialViewController];
self.window.rootViewController = mainVc;
[[WCXMPPTool sharedWCXMPPTool] xmppLogin:nil];
}
return YES;
}
以上。