一春叫、用戶登錄流程
- 注意:XMPP核心文件噪矛,
基于TCP的XML流的傳輸
小渊,XMPPFrame框架是通過代理的方式實現(xiàn)消息傳遞的
實現(xiàn)用戶登錄的步驟如下:
- 1放可、
實例化
XMPPStream并設(shè)置代理
谒臼,同時添加代理到工作隊列 - 2、使用JID
連接至服務(wù)器
耀里,默認端口為5222蜈缤,JID字符串中需要包含服務(wù)器的域名 - 3、 在完成連接的代理方法中
驗證用戶密碼
备韧,連接完成后XMPPStream的isConnect屬性為YES
+4劫樟、 在驗證代理方法中判斷用戶是否登錄成功 - 5、上線或者下線成功后,向服務(wù)器發(fā)送Presence數(shù)據(jù)叠艳,以
更新用戶
在服務(wù)器的狀態(tài)
二奶陈、注意
- 為了簡化開發(fā),XMPP的引用程序通常會將XMPPStream放置在AppDelegate中,以便于全局訪問
三阐滩、分析
1闺金、封裝登錄工具類 JPLoginTool
- 利用工具類,保存用戶登錄信息到沙盒中
- 頭文件 .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;
/**
* 獲取用戶登錄狀態(tài)
*
*/
+(BOOL)isLogin;
/**
* 設(shè)置用戶登錄狀態(tài)
*/
+(void)setLogin:(BOOL)login;
@end
- .m實現(xiàn)文件
#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];
}
/**
* 獲取用戶登錄狀態(tài)
*
*/
+(BOOL)isLogin{
return [[NSUserDefaults standardUserDefaults] boolForKey:kIsLoginKey];
}
/**
* 設(shè)置用戶登錄狀態(tài)
*/
+(void)setLogin:(BOOL)login{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:login forKey:kIsLoginKey];
[defaults synchronize];
}
@end
2徐勃、AppDelegate.h 文件中
- 1、首先早像,我們布局登錄界面僻肖,提供登錄按鈕,當我們點擊登錄按鈕時就進行登錄操作卢鹦,因為XMPPFrame的相關(guān)操作都建議寫在AppDelegate文件中臀脏,所以我們需要在AppDelegate.h文件中提供接口給用戶,而至于那些連接到服務(wù)器以及授權(quán)等操作都寫在AppDelegate.m中不用暴露給用戶冀自。
#pragma mark 用戶登錄
-(void)xmppUserLogin;
- 2揉稚、但是,當我們點擊登錄熬粗,在控制器的登錄方法中搀玖,拿到應(yīng)用程序的代理,然后調(diào)用應(yīng)用代理的xmppUserLogin方法驻呐,此時不僅要執(zhí)行登錄操作灌诅,而且應(yīng)用代理必須要告訴我們是否登錄成功,因為登錄操作是封裝到AppDelegate中的 - > 這就要實現(xiàn)兩者之間的通信了暴氏。
- 有多種通信方式延塑,通知/代理/block/action等,這里我選擇代理答渔,所以重新設(shè)置應(yīng)用代理提供的登錄接口為:
// 在應(yīng)用代理頭文件中
// 1. 定義block
typedef enum {
XMPPResultTypeLogining,//正在登錄中
XMPPResultTypeLoginSuccess,//登錄成功
XMPPResultTypeLoginFailure,//登錄失敗
XMPPResultTypeNetError,//網(wǎng)絡(luò)不給力
XMPPResultTypeUnknowDomain,//域名不存在
XMPPResultTypeConnectionRefused//服務(wù)器拒絕連接
} XMPPResultType;
typedef void (^XMPPResultBlock)(XMPPResultType resultType);//xmpp請求結(jié)果的block
// 2.提供為外界登錄接口
#pragma mark 用戶登錄
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;
- 3关带、用戶登錄成功后,可以點擊控制器視圖中的注銷按鈕進行注銷操作沼撕,所以我們在AppDelegate中提供了一個注銷方法接口給外界
#pragma 用戶注銷
-(void)xmppUserLogout;
- 4宋雏、當我們的用戶狀態(tài)改變的時候,要告訴外界务豺,比如磨总,用戶在線時,需要一些處理笼沥,所以我們不僅需要將用戶的登錄信息寫入到沙盒中蚪燕,而且用戶的狀態(tài)也要寫入到沙盒中娶牌,第二個問題:當我們的用戶狀態(tài)改變的時候,我們要告訴控制器馆纳,讓控制器進行一些處理操作:又牽扯到通信了诗良,這里:采用通知告知吧
//登錄狀態(tài)改變
#define kLoginStateChangeNotification @"LoginStateChangeNotification"
- 5、AppDelegate.h頭文件
#import <UIKit/UIKit.h>
#define kIsLoginKey @"isLogin"
//登錄狀態(tài)改變
#define kLoginStateChangeNotification @"LoginStateChangeNotification"
#define xmppDelegate ((JPAppDelegate *)[UIApplication sharedApplication].delegate)
typedef enum {
XMPPResultTypeLogining,//正在登錄中
XMPPResultTypeLoginSuccess,//登錄成功
XMPPResultTypeLoginFailure,//登錄失敗
XMPPResultTypeNetError,//網(wǎng)絡(luò)不給力
XMPPResultTypeUnknowDomain,//域名不存在
XMPPResultTypeConnectionRefused//服務(wù)器拒絕連接
}XMPPResultType;
typedef void (^XMPPResultBlock)(XMPPResultType resultType);//xmpp請求結(jié)果的block
@interface JPAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
#pragma mark 用戶登錄
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;
#pragma 用戶注銷
-(void)xmppUserLogout;
@end
三鲁驶、AppDelegate.m實現(xiàn)文件
0.在登錄界面點擊登錄按鈕鉴裹,登錄成功后,進入主界面
1.用戶登錄成功后钥弯,退出到后臺時径荔,斷開連接,顯示在前臺時自動連接(添加一個isLogin用戶偏好設(shè)置)
2.用戶成功登錄后脆霎,如果是重新啟動程序总处,直接跳到主界面,否則跳到登錄頁面
3.用戶成功登錄后睛蛛,如果是重新啟動程序辨泳,下次啟動時自動登錄
4.用戶登錄失敗時,清除偏好設(shè)置
5.用戶登錄失敗時要提示
6.用戶注銷
7.進入登錄頁面時玖院,自動顯示上一次登錄數(shù)據(jù)
-
注意:
- 官方建議,把用戶授權(quán)寫在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;
//授權(quán)(也就發(fā)送帳號和密碼)
-(void)userAuth;
//用戶上線(通知其他好友,你已經(jīng)在線)
-(void)goOnline;
//用戶用戶離線
-(void)goOffline;
@end
@implementation JPAppDelegate
//官方建議,把用戶授權(quán)寫在appdelegate
/**
* 0.初始化xmppStream
* 1.連接主機
* 2.從主機斷開連接
* 3.授權(quán)(也就發(fā)送帳號和密碼)
* 4.用戶上線(通知其他好友,你已經(jīng)在線)
* 5.用戶用戶離線
*/
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//NSLog(@"%s %d",__func__,__LINE__);
//設(shè)置日志輸出方式(輸出到控制器)
[DDLog addLogger:[DDTTYLogger sharedInstance]];
//設(shè)置顏色
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
//設(shè)置自己的日志顏色
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:LOG_LEVEL_INFO];
// 初始化xmppStream
[self setupXmppStream];
return YES;
}
#pragma 失去焦點
- (void)applicationWillResignActive:(UIApplication *)application
{
//從主機斷開連接
//[self disconnectFromHost];
//獲取登錄狀態(tài)
if([JPLoginTool isLogin]){
//NSLog(@"登錄過");
//如果是登錄 斷開連接
[self disconnectFromHost];
};
}
#pragma mark 獲取焦點
- (void)applicationDidBecomeActive:(UIApplication *)application
{
//[self connectToHost];
//獲取登錄狀態(tài) 如果登錄過,直接跳到主頁面 然后自動登錄
if([JPLoginTool isLogin]){
NSLog(@"登錄過");
//直接跳到主頁
[self showStoryboardWithName:kMainStoryboardName];
//自動登錄
[self connectToHost];
};
}
#pragma mark - xmppSteam代理
#pragma mark 連接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
JPLogInfo(@"與主機連接成功");
//JPLogInfo(@"登錄");
//發(fā)送登錄密碼驗證
[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);
}
}
// 清除用戶偏好設(shè)置
if (error) {
[JPLoginTool removeAllLoginInfo];
}
}
#pragma mark 用戶授權(quán)成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
// 子線程
NSLog(@"%@",[NSThread currentThread]);
JPLogInfo(@"用戶授權(quán)成功");
// 把"登錄狀態(tài)"通知"聊天歷史控制器"
// 登錄成功
[self postLoginNotification:XMPPResultTypeLoginSuccess];
// 1.通知用戶上線
[self goOnline];
// 2.如果用戶登錄成功 沙盒里保存一個登錄狀態(tài)
[JPLoginTool setLogin:YES];
//在主線程更新UI
[self showStoryboardWithName:kMainStoryboardName];
}
#pragma mark 用戶授權(quán)失敗
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
JPLogInfo(@"用戶授權(quán)失敗%@",error);
//把"登錄狀態(tài)"通知"聊天歷史控制器"
//登錄失敗
[self postLoginNotification:XMPPResultTypeLoginFailure];
//清除用戶偏好設(shè)置
[JPLoginTool removeAllLoginInfo];
//通知登錄控制器
if (_resultBlock) {
_resultBlock(XMPPResultTypeLoginFailure);
}
// dispatch_async(dispatch_get_main_queue(), ^{
//
// });
}
#pragma mark -私有方法
#pragma mark 初始化xmppStrem對象
-(void)setupXmppStream{
NSAssert(_xmppStream == nil, @"xmppStream對象初始化多次");
//1.創(chuàng)建xmppStrem對象
_xmppStream = [[XMPPStream alloc] init];
//2.添加代表
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
-(void)connectToHost{
JPLogInfo(@"開始連接到主機");
//把"登錄狀態(tài)"通知"聊天歷史控制器"
//登錄中
[self postLoginNotification:XMPPResultTypeLogining];
//從沙盒里獲取數(shù)據(jù)
NSString *account = [JPLoginTool account];
NSString *domain = [JPLoginTool domain];
//1.設(shè)置賬號
_xmppStream.myJID = [XMPPJID jidWithUser:account domain:domain resource:nil];
//2.設(shè)置主機
_xmppStream.hostName = domain;
//3.設(shè)置主機端口
//默認端口就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 用戶授權(quán)
-(void)userAuth{
JPLogInfo(@"用戶開始授權(quán)");
NSError *error = nil;
//發(fā)送密碼到服務(wù)器
[_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的第一個控制器
// 比如用戶登錄成功后,設(shè)置主窗口的根控制器為tabVC第岖,然后难菌,選中的tabVC的第三個子控制器,
// 然后退出到后臺蔑滓,當用戶再次進入到前臺時郊酒,并不需要重新設(shè)置窗口的根控制器此時。
// 那什么時候需要設(shè)置呢-> 當主窗口的根控制器不是tabVC才需要重新設(shè)置窗口的根控制器
-(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(@"用戶登錄被調(diào)用");
//如果當前socket存在連接,應(yīng)該斷開
// if (_xmppStream.isConnected) {
// [_xmppStream disconnect];
// }
[_xmppStream disconnect];
[self connectToHost];
}
#pragma mark 用戶注銷
-(void)xmppUserLogout{
//1.設(shè)置登錄狀態(tài)為NO
[JPLoginTool setLogin:NO];
//2.斷開連接
[self disconnectFromHost];
//3.返回到登錄頁面
[self showStoryboardWithName:@"Login"];
}
#pragma mark 登錄狀態(tài)通知
-(void)postLoginNotification:(XMPPResultType)resultType{
//把"登錄狀態(tài)"通知"聊天歷史控制器"
//登錄成功
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{@"LoginState": @(resultType)};
[[NSNotificationCenter defaultCenter] postNotificationName:kLoginStateChangeNotification object:nil userInfo:userInfo];
});
}
四键袱、控制器中實現(xiàn)登錄
- (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.調(diào)用appdelegate里的xmmpUserLogin方法
//JPAppDelegate *delegate = [UIApplication sharedApplication].delegate;
[xmppDelegate xmppUserLogin:^(XMPPResultType resultType) {
//隱藏正在登錄
//因為這個block是被appdelegate里面xmppStream的代理調(diào)用,而xmppStream代理被調(diào)用是在子線線程中的,所在更新UI放在主線程
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:showView];
switch (resultType) {
case XMPPResultTypeLoginFailure:
[MBProgressHUD showError:@"用戶名或者密碼錯誤"];
break;
case XMPPResultTypeNetError:
[MBProgressHUD showError:@"網(wǎng)絡(luò)不給力"];
break;
case XMPPResultTypeConnectionRefused:
[MBProgressHUD showError:@"服務(wù)器拒絕連接,可能服務(wù)沒有開啟"];
break;
case XMPPResultTypeUnknowDomain:
JPLogInfo(@"域名不存在或者錯誤");
[MBProgressHUD showError:@"域名不存在或者錯誤"];
break;
default:
break;
}
});
}];
}