一、用戶登錄流程 用戶登錄流程.png 註意:XMPP核心文件,基於TCP的XML流的傳輸,XMPPFrame框架是通過代理的方式實現消息傳遞的 實現用戶登錄的步驟如下: 1、實例化XMPPStream並設置代理,同時添加代理到工作隊列 2、使用JID連接至伺服器,預設埠為5222,JID字元串中 ...
一、用戶登錄流程
用戶登錄流程.png
- 註意:XMPP核心文件,
基於TCP的XML流的傳輸
,XMPPFrame框架是通過代理的方式實現消息傳遞的
實現用戶登錄的步驟如下:
- 1、
實例化
XMPPStream並設置代理
,同時添加代理到工作隊列 - 2、使用JID
連接至伺服器
,預設埠為5222,JID字元串中需要包含伺服器的功能變數名稱 - 3、 在完成連接的代理方法中
驗證用戶密碼
,連接完成後XMPPStream的isConnect屬性為YES
+4、 在驗證代理方法中判斷用戶是否登錄成功 - 5、上線或者下線成功後,向伺服器發送Presence數據,以
更新用戶
在伺服器的狀態
二、註意
- 為了簡化開發,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;
/**
* 獲取用戶登錄狀態
*
*/
+(BOOL)isLogin;
/**
* 設置用戶登錄狀態
*/
+(void)setLogin:(BOOL)login;
@end
- .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;
}
});
}];
}
原文鏈接:http://www.jianshu.com/p/a16d3d70dd86
參考鏈接:http://blog.csdn.net/cerastes/article/details/33713967
http://blog.csdn.net/liuhongwei123888/article/details/6840262
http://www.jianshu.com/p/a22406c40c8b
源碼地址:https://github.com/robbiehanson/XMPPFramework,目前需要使用 Git (sourcTree) 才能download到源碼。