iOS XMPP——登录

一、用户登录流程

iOS XMPP——登录_第1张图片
用户登录流程.png
  • 注意:XMPP核心文件,基于TCP的XML流的传输,XMPPFrame框架是通过代理的方式实现消息传递的

实现用户登录的步骤如下:

  • 1、实例化XMPPStream并设置代理,同时添加代理到工作队列
  • 2、使用JID连接至服务器,默认端口为5222,JID字符串中需要包含服务器的域名
  • 3、 在完成连接的代理方法中验证用户密码,连接完成后XMPPStream的isConnect属性为YES
    +4、 在验证代理方法中判断用户是否登录成功
  • 5、上线或者下线成功后,向服务器发送Presence数据,以更新用户在服务器的状态

二、注意

  • 为了简化开发,XMPP的引用程序通常会将XMPPStream放置在AppDelegate中,以便于全局访问

三、分析

1、封装登录工具类 JPLoginTool

  • 利用工具类,保存用户登录信息到沙盒中
    1. 头文件 .h
#import 

@interface JPLoginTool : NSObject

/**
 *  保存登录信息到沙盒
 */
+(void)saveLoginInfoWithAccount:(NSString *)account pwd:(NSString *)pwd domain:(NSString *)domain;

/**
 *  获取沙盒的帐号
 *
 */
+(NSString *)account;

/**
 *  获取沙盒的密码
 *
 */
+(NSString *)password;


/**
 *  获取沙盒的域名
 *
 */
+(NSString *)domain;

/**
 *  从沙盒清除所有的用户登录信息
 */
+(void)removeAllLoginInfo;

/**
 *  获取用户登录状态
 *
 */
+(BOOL)isLogin;


/**
 *  设置用户登录状态
 */
+(void)setLogin:(BOOL)login;
@end
    1. .m实现文件
#import "JPLoginTool.h"

#define kAccountKey @"account"
#define kPasswordKey @"passsword"
#define kDomainKey @"domain"
#define kIsLoginKey @"isLogin"


@implementation JPLoginTool

+(void)saveLoginInfoWithAccount:(NSString *)account pwd:(NSString *)pwd domain:(NSString *)domain{
    NSUserDefaults *defaults =  [NSUserDefaults standardUserDefaults];
    [defaults setObject:account forKey:kAccountKey];
    [defaults setObject:pwd forKey:kPasswordKey];
    [defaults setObject:domain forKey:kDomainKey];
    
    //同步沙盒
    [defaults synchronize];
}

/**
 *  获取沙盒的帐号
 *
 */
+(NSString *)account{
    
    return [[NSUserDefaults standardUserDefaults] objectForKey:kAccountKey];
}

/**
 *  获取沙盒的密码
 *
 */
+(NSString *)password{
    return [[NSUserDefaults standardUserDefaults] objectForKey:kPasswordKey];
}

/**
 *  获取沙盒的域名
 *
 */
+(NSString *)domain{
    return [[NSUserDefaults standardUserDefaults] objectForKey:kDomainKey];
}

/**
 *  从沙盒清除所有的用户登录信息
 *
 */
+(void)removeAllLoginInfo{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObjectForKey:kAccountKey];
    [defaults removeObjectForKey:kPasswordKey];
    [defaults removeObjectForKey:kDomainKey];
    [defaults synchronize];
}


/**
 *  获取用户登录状态
 *
 */
+(BOOL)isLogin{
    return [[NSUserDefaults standardUserDefaults] boolForKey:kIsLoginKey];
}


/**
 *  设置用户登录状态
 */
+(void)setLogin:(BOOL)login{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:login forKey:kIsLoginKey];
    [defaults synchronize];
}
@end

2、AppDelegate.h 文件中

  • 1、首先,我们布局登录界面,提供登录按钮,当我们点击登录按钮时就进行登录操作,因为XMPPFrame的相关操作都建议写在AppDelegate文件中,所以我们需要在AppDelegate.h文件中提供接口给用户,而至于那些连接到服务器以及授权等操作都写在AppDelegate.m中不用暴露给用户。
#pragma mark 用户登录
-(void)xmppUserLogin;
  • 2、但是,当我们点击登录,在控制器的登录方法中,拿到应用程序的代理,然后调用应用代理的xmppUserLogin方法,此时不仅要执行登录操作,而且应用代理必须要告诉我们是否登录成功,因为登录操作是封装到AppDelegate中的 - > 这就要实现两者之间的通信了。
    • 有多种通信方式,通知/代理/block/action等,这里我选择代理,所以重新设置应用代理提供的登录接口为:
// 在应用代理头文件中

// 1. 定义block

typedef enum {
    XMPPResultTypeLogining,//正在登录中
    XMPPResultTypeLoginSuccess,//登录成功
    XMPPResultTypeLoginFailure,//登录失败
    XMPPResultTypeNetError,//网络不给力
    XMPPResultTypeUnknowDomain,//域名不存在
    XMPPResultTypeConnectionRefused//服务器拒绝连接
} XMPPResultType;

typedef void (^XMPPResultBlock)(XMPPResultType resultType);//xmpp请求结果的block

// 2.提供为外界登录接口
#pragma mark 用户登录
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;
  • 3、用户登录成功后,可以点击控制器视图中的注销按钮进行注销操作,所以我们在AppDelegate中提供了一个注销方法接口给外界
#pragma 用户注销
-(void)xmppUserLogout;
  • 4、当我们的用户状态改变的时候,要告诉外界,比如,用户在线时,需要一些处理,所以我们不仅需要将用户的登录信息写入到沙盒中,而且用户的状态也要写入到沙盒中,第二个问题:当我们的用户状态改变的时候,我们要告诉控制器,让控制器进行一些处理操作:又牵扯到通信了,这里:采用通知告知吧
//登录状态改变
#define kLoginStateChangeNotification @"LoginStateChangeNotification"
  • 5、AppDelegate.h头文件
#import 

#define kIsLoginKey @"isLogin"

//登录状态改变
#define kLoginStateChangeNotification @"LoginStateChangeNotification"

#define xmppDelegate ((JPAppDelegate *)[UIApplication sharedApplication].delegate)

typedef enum {
    XMPPResultTypeLogining,//正在登录中
    XMPPResultTypeLoginSuccess,//登录成功
    XMPPResultTypeLoginFailure,//登录失败
    XMPPResultTypeNetError,//网络不给力
    XMPPResultTypeUnknowDomain,//域名不存在
    XMPPResultTypeConnectionRefused//服务器拒绝连接
}XMPPResultType;
typedef void (^XMPPResultBlock)(XMPPResultType resultType);//xmpp请求结果的block

@interface JPAppDelegate : UIResponder 

@property (strong, nonatomic) UIWindow *window;


#pragma mark 用户登录
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;


#pragma 用户注销
-(void)xmppUserLogout;

@end

三、AppDelegate.m实现文件

  • 0.在登录界面点击登录按钮,登录成功后,进入主界面

  • 1.用户登录成功后,退出到后台时,断开连接,显示在前台时自动连接(添加一个isLogin用户偏好设置)

  • 2.用户成功登录后,如果是重新启动程序,直接跳到主界面,否则跳到登录页面

  • 3.用户成功登录后,如果是重新启动程序,下次启动时自动登录

  • 4.用户登录失败时,清除偏好设置

  • 5.用户登录失败时要提示

  • 6.用户注销

  • 7.进入登录页面时,自动显示上一次登录数据

  • 注意:

    • 官方建议,把用户授权写在appdelegate
#import "JPAppDelegate.h"
#import "JPLoginTool.h"


#define kMainStoryboardName @"Main"
#define kLoginStoryboardName @"Login"

@interface JPAppDelegate(){
    
    XMPPResultBlock _resultBlock;
}

//初始化xmppStream
-(void)setupXmppStream;

//连接主机
-(void)connectToHost;

//从主机断开连接
-(void)disconnectFromHost;

//授权(也就发送帐号和密码)
-(void)userAuth;

//用户上线(通知其他好友,你已经在线)
-(void)goOnline;

//用户用户离线
-(void)goOffline;

@end

@implementation JPAppDelegate

//官方建议,把用户授权写在appdelegate
/**
 *  0.初始化xmppStream
 *  1.连接主机
 *  2.从主机断开连接
 *  3.授权(也就发送帐号和密码)
 *  4.用户上线(通知其他好友,你已经在线)
 *  5.用户用户离线
 */


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    //NSLog(@"%s %d",__func__,__LINE__);
    
    //设置日志输出方式(输出到控制器)
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
    
    //设置颜色
    [[DDTTYLogger sharedInstance] setColorsEnabled:YES];
    
    
    //设置自己的日志颜色
    [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:LOG_LEVEL_INFO];
    
    // 初始化xmppStream
    [self setupXmppStream];
    
    return YES;
}
#pragma 失去焦点
- (void)applicationWillResignActive:(UIApplication *)application
{
    //从主机断开连接
    //[self disconnectFromHost];
    
    //获取登录状态
    if([JPLoginTool isLogin]){
        //NSLog(@"登录过");
        //如果是登录 断开连接
        [self disconnectFromHost];
    };
}



#pragma mark 获取焦点
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    //[self connectToHost];
    //获取登录状态 如果登录过,直接跳到主页面 然后自动登录
    if([JPLoginTool isLogin]){
        NSLog(@"登录过");
        //直接跳到主页
        [self showStoryboardWithName:kMainStoryboardName];
        
        //自动登录
        [self connectToHost];
    };
}

#pragma mark - xmppSteam代理
#pragma mark 连接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    JPLogInfo(@"与主机连接成功");
     //JPLogInfo(@"登录");
     //发送登录密码验证
        [self userAuth];
}

#pragma mark 连接失败
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    
    //如果error为空,代理正常断开
    JPLogInfo(@"连接失败 %@",error);
    
    //有error 并且block有值 通知登录控制器
    
    if (error && _resultBlock) {
        //域名或者主机不存
        if (error.code == 8) {
            _resultBlock(XMPPResultTypeUnknowDomain);
        }else if(error.code == 61){
            _resultBlock(XMPPResultTypeConnectionRefused);
        } else{
            _resultBlock(XMPPResultTypeNetError);
        }
    }
    
    // 清除用户偏好设置
    if (error) {
        [JPLoginTool removeAllLoginInfo];
    }
}

#pragma mark 用户授权成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
   // 子线程
    NSLog(@"%@",[NSThread currentThread]);
    JPLogInfo(@"用户授权成功");
    // 把"登录状态"通知"聊天历史控制器"
    // 登录成功
    [self postLoginNotification:XMPPResultTypeLoginSuccess];
    
    // 1.通知用户上线
    [self goOnline];
    
    // 2.如果用户登录成功 沙盒里保存一个登录状态
    [JPLoginTool setLogin:YES];

    //在主线程更新UI
    [self showStoryboardWithName:kMainStoryboardName];
}

#pragma mark 用户授权失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    JPLogInfo(@"用户授权失败%@",error);
    //把"登录状态"通知"聊天历史控制器"
    //登录失败
    [self postLoginNotification:XMPPResultTypeLoginFailure];
   
    //清除用户偏好设置
    [JPLoginTool removeAllLoginInfo];
    //通知登录控制器
    if (_resultBlock) {
        _resultBlock(XMPPResultTypeLoginFailure);
    }
//    dispatch_async(dispatch_get_main_queue(), ^{
//        
//    });
}

#pragma mark -私有方法

#pragma mark 初始化xmppStrem对象
-(void)setupXmppStream{
    
    NSAssert(_xmppStream == nil, @"xmppStream对象初始化多次");
    
    //1.创建xmppStrem对象
    _xmppStream = [[XMPPStream alloc] init];
    //2.添加代表
    [_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}

-(void)connectToHost{
    JPLogInfo(@"开始连接到主机");
    
    //把"登录状态"通知"聊天历史控制器"
    //登录中
    [self postLoginNotification:XMPPResultTypeLogining];

    //从沙盒里获取数据
    NSString *account =  [JPLoginTool account];
    NSString *domain = [JPLoginTool domain];
    
    //1.设置账号
    _xmppStream.myJID = [XMPPJID jidWithUser:account domain:domain resource:nil];
    
    //2.设置主机
    _xmppStream.hostName = domain;
    
    //3.设置主机端口
    //默认端口就5222
    _xmppStream.hostPort = 5222;
    //连接到主机
    
    NSError *err = nil;
    [_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err];
    if (err) {
       JPLogInfo(@"%@ ",err);
    }
}

#pragma mark 从主机断连接
-(void)disconnectFromHost{
    // 1.通知用户下线
    [self goOffline];
    
    // 2.断开连接
    [_xmppStream disconnect];
}

#pragma mark 用户授权
-(void)userAuth{
    JPLogInfo(@"用户开始授权");
     
    NSError *error = nil;
    //发送密码到服务器
    [_xmppStream authenticateWithPassword:[JPLoginTool password] error:&error];
    
    if (error) {
        JPLogInfo(@"%@",error);
    }
}

#pragma mark 用户上线
-(void)goOnline{
    JPLogInfo(@"通知用户在线");
    XMPPPresence *presence  = [XMPPPresence presence];
    [_xmppStream sendElement:presence];
}

#pragma mark 用户下线
-(void)goOffline{
    JPLogInfo(@"通知用户下线");
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
    [_xmppStream sendElement:presence];
}

#pragma mark 显示storyboard的第一个控制器
// 比如用户登录成功后,设置主窗口的根控制器为tabVC,然后,选中的tabVC的第三个子控制器,
// 然后退出到后台,当用户再次进入到前台时,并不需要重新设置窗口的根控制器此时。
// 那什么时候需要设置呢-> 当主窗口的根控制器不是tabVC才需要重新设置窗口的根控制器
-(void)showStoryboardWithName:(NSString *)name{
    dispatch_async(dispatch_get_main_queue(), ^{
        // 3.显示主界面
        // 3.1获取sotryboard对象
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
        
        //3.2获取windows的旧的根控制器
        UIViewController *oldVc = self.window.rootViewController;
        JPLogWarn(@"旧的控制器 %@",oldVc);
        
        //3.3获取要切换storyboard里面的第一个控制器
        UIViewController *newVc = [storyboard instantiateInitialViewController];
        
        JPLogWarn(@"要切换的控制器 %@",newVc);
        
        //如果旧的控制器类型和新的控制器类型不一样,才须要切换UIWindow的根控制器
        if (![oldVc isKindOfClass:[newVc class]]) {
            //38
            self.window.rootViewController = newVc;
        }
    });
}


#pragma mark -公共方法
#pragma mark 用户登录
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock{
    //block负值
    _resultBlock = resultBlock;
    
    JPLogInfo(@"用户登录被调用");
    //如果当前socket存在连接,应该断开
//    if (_xmppStream.isConnected) {
//        [_xmppStream disconnect];
//    }
    
    [_xmppStream disconnect];
    [self connectToHost];
}


#pragma mark 用户注销
-(void)xmppUserLogout{
    //1.设置登录状态为NO
    [JPLoginTool setLogin:NO];
    
    //2.断开连接
    [self disconnectFromHost];
    
    //3.返回到登录页面
    [self showStoryboardWithName:@"Login"];
}

#pragma mark 登录状态通知
-(void)postLoginNotification:(XMPPResultType)resultType{
    //把"登录状态"通知"聊天历史控制器"
    //登录成功
    dispatch_async(dispatch_get_main_queue(), ^{
        NSDictionary *userInfo = @{@"LoginState": @(resultType)};
        
        [[NSNotificationCenter defaultCenter] postNotificationName:kLoginStateChangeNotification object:nil userInfo:userInfo];
    });
}

四、控制器中实现登录

- (IBAction)login {
    
    //1.把你登录信息保存到沙盒里
    NSString *account = self.accountField.text;
    NSString *password = self.passwordFiled.text;
    NSString *domain = self.domainField.text;
    
    [JPLoginTool saveLoginInfoWithAccount:account pwd:password domain:domain];
    
    //隐藏keyboard
    [self.view endEditing:YES];
    
    //提醒正在登录
    UIView *showView = self.view;
    [MBProgressHUD showMessage:@"正在登录......" toView:showView];
    
    
    xmppDelegate.userRegister = NO;//代表登录
    
    //2.调用appdelegate里的xmmpUserLogin方法
    //JPAppDelegate *delegate = [UIApplication sharedApplication].delegate;
    [xmppDelegate xmppUserLogin:^(XMPPResultType resultType) {
        //隐藏正在登录
        //因为这个block是被appdelegate里面xmppStream的代理调用,而xmppStream代理被调用是在子线线程中的,所在更新UI放在主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUD hideHUDForView:showView];
            switch (resultType) {
                case XMPPResultTypeLoginFailure:
                    [MBProgressHUD showError:@"用户名或者密码错误"];
                    break;
                case XMPPResultTypeNetError:
                    [MBProgressHUD showError:@"网络不给力"];
                    break;
                case XMPPResultTypeConnectionRefused:
                    [MBProgressHUD showError:@"服务器拒绝连接,可能服务没有开启"];
                    break;
                case XMPPResultTypeUnknowDomain:
                    JPLogInfo(@"域名不存在或者错误");
                    [MBProgressHUD showError:@"域名不存在或者错误"];
                    break;
                default:
                    break;
            }
        });
       
    }];
}

你可能感兴趣的:(iOS XMPP——登录)