iOS XMPP——登录

作者: iOS_成才录 | 来源:发表于2015-11-12 19:28 被阅读2151次

    一、用户登录流程

    用户登录流程.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 <Foundation/Foundation.h>
    
    @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 <UIKit/UIKit.h>
    
    #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 <UIApplicationDelegate>
    
    @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()<XMPPStreamDelegate>{
        
        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;
                }
            });
           
        }];
    }
    

    相关文章

      网友评论

      • Jayne_Kuo:求xmpp发送语音的经,求指点!大神!
      • b28291cdeddc:您好,请问怎么在客户端指定密码机制为 plain? :pray:
      • 胖子程:看你目前是999喜欢,那我助你一臂之力!
      • o小霸道o:写的非常不错
      • 闻道刘:非常好的文章。

      本文标题:iOS XMPP——登录

      本文链接:https://www.haomeiwen.com/subject/kqmchttx.html