登录功能完成以后包含以下代码文件:
AppDelegate.h
AppDelegate.m
LoginViewController.h
LoginViewController.m
LoginUser.h
LoginUser.m
以下看代码:
//
// AppDelegate.h
// XMPP即时通讯
//
// Created by Mac on 15/7/15.
// Copyright (c) 2015年 聂小波. All rights reserved.
// #import <UIKit/UIKit.h>
#import "XMPPFramework.h" #define xmppDelegate (AppDelegate *)[[UIApplication sharedApplication] delegate] typedef void(^CompletionBlock)(); @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; #pragma mark - XMPP属性及方法
/**
* 全局的XMPPStream,只读属性
*/
@property (strong, nonatomic, readonly) XMPPStream *xmppStream;
/**
* 全局的xmppvCard模块,只读属性
*/
@property (strong, nonatomic, readonly) XMPPvCardTempModule *xmppvCardModule;
/**
* 全局的XMPPvCardAvatar模块,只读属性
*/
@property (strong, nonatomic, readonly) XMPPvCardAvatarModule *xmppvCardAvatarModule;
/**
* 全局的xmppRoster模块,只读属性
*/
@property (strong, nonatomic, readonly) XMPPRoster *xmppRoster;
/**
* 全局的XMPPRosterCoreDataStorage模块,只读属性
*/
@property (strong, nonatomic, readonly) XMPPRosterCoreDataStorage *xmppRosterStorage; /**
* 消息存档(归档)模块,只读属性
*/
@property (strong, nonatomic, readonly) XMPPMessageArchiving *xmppMessageArchiving;
@property (strong, nonatomic, readonly) XMPPMessageArchivingCoreDataStorage *xmppMessageArchivingCoreDataStorage; /**
* 传输文件socket数组
*/
@property (strong, nonatomic) NSMutableArray *socketList; /**
* 是否注册用户标示
*/
@property (assign, nonatomic) BOOL isRegisterUser; /**
* 连接到服务器
*
* 注释:用户信息保存在系统偏好中
*
* @param completion 连接正确的块代码
* @param faild 连接错误的块代码
*/
- (void)connectWithCompletion:(CompletionBlock)completion failed:(CompletionBlock)faild; /**
* 注销用户登录
*/
- (void)logout; @end
//
// AppDelegate.m
// XMPP即时通讯
//
// Created by Mac on 15/7/15.
// Copyright (c) 2015年 聂小波. All rights reserved.
// #import "AppDelegate.h"
#import "LoginUser.h"
#import "NSString+Helper.h"
#import "NSData+XMPP.h" #define kNotificationUserLogonState @"NotificationUserLogon" @interface AppDelegate() <XMPPStreamDelegate, XMPPRosterDelegate, TURNSocketDelegate>
{
CompletionBlock _completionBlock; // 成功的块代码
CompletionBlock _faildBlock; // 失败的块代码 XMPPReconnect *_xmppReconnect; // XMPP重新连接XMPPStream
XMPPvCardCoreDataStorage *_xmppvCardStorage; // 电子名片的数据存储模块 XMPPCapabilities *_xmppCapabilities; // 实体扩展模块
XMPPCapabilitiesCoreDataStorage *_xmppCapabilitiesCoreDataStorage; // 数据存储模块
} // 设置XMPPStream
- (void)setupStream;
// 销毁XMPPStream并注销已注册的扩展模块
- (void)teardownStream;
// 通知服务器器用户上线
- (void)goOnline;
// 通知服务器器用户下线
- (void)goOffline;
// 连接到服务器
- (void)connect;
// 与服务器断开连接
- (void)disconnect; @end @implementation AppDelegate #pragma mark 根据用户登录状态加载对应的Storyboard显示
- (void)showStoryboardWithLogonState:(BOOL)isUserLogon
{
UIStoryboard *storyboard = nil; if (isUserLogon) {
// 显示Main.storyboard
storyboard = [UIStoryboard storyboardWithName:@"LoginViewController" bundle:nil];
} else {
// 显示Login.sotryboard
storyboard = [UIStoryboard storyboardWithName:@"LoginViewController" bundle:nil];
} // 在主线程队列负责切换Storyboard,而不影响后台代理的数据处理
dispatch_async(dispatch_get_main_queue(), ^{
// 如果在项目属性中,没有指定主界面(启动的Storyboard,self.window不会被实例化)
// 把Storyboard的初始视图控制器设置为window的rootViewController
[self.window setRootViewController:storyboard.instantiateInitialViewController]; if (!self.window.isKeyWindow) {
[self.window makeKeyAndVisible];
}
});
} #pragma mark - AppDelegate方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 1. 实例化window
// self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 2. 设置XMPPStream
[self setupStream]; // 3. 实例化socket数组
_socketList = [NSMutableArray array]; return YES;
} - (void)applicationWillResignActive:(UIApplication *)application
{
[self disconnect];
} - (void)applicationDidBecomeActive:(UIApplication *)application
{
// 应用程序被激活后,直接连接,使用系统偏好中的保存的用户记录登录
// 从而实现自动登录的效果!
[self connect];
} - (void)dealloc
{
// 释放XMPP相关对象及扩展模块
[self teardownStream];
} #pragma mark - XMPP相关方法
// 设置XMPPStream
- (void)setupStream
{
// 0. 方法被调用时,要求_xmppStream必须为nil,否则通过断言提示程序员,并终止程序运行!
// NSAssert(_xmppStream == nil, @"XMPPStream被多次实例化!"); // 1. 实例化XMPPSteam
_xmppStream = [[XMPPStream alloc] init];
// _xmppStream.hostName = @"127.0.0.1";
////// mysql://localhost:3306/openfire
// _xmppStream.hostPort = 5222;
//
////// NSString *myJID=@"admin";
// _xmppStream.myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"admin@127.0.0.1"]]; // 让XMPP在真机运行时支持后台,在模拟器上是不支持后台服务运行的
#if !TARGET_IPHONE_SIMULATOR
{
// 允许XMPPStream在真机运行时,支持后台网络通讯!
[_xmppStream setEnableBackgroundingOnSocket:YES];
}
#endif // 2. 扩展模块
// 2.1 重新连接模块
_xmppReconnect = [[XMPPReconnect alloc] init];
// 2.2 电子名片模块
_xmppvCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
_xmppvCardModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:_xmppvCardStorage];
_xmppvCardAvatarModule = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_xmppvCardModule]; // 2.4 花名册模块
_xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
_xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:_xmppRosterStorage];
// 设置自动接收好友订阅请求
[_xmppRoster setAutoAcceptKnownPresenceSubscriptionRequests:YES];
// 自动从服务器更新好友记录,例如:好友自己更改了名片
[_xmppRoster setAutoFetchRoster:YES]; // 2.5 实体扩展模块
_xmppCapabilitiesCoreDataStorage = [[XMPPCapabilitiesCoreDataStorage alloc] init];
_xmppCapabilities = [[XMPPCapabilities alloc] initWithCapabilitiesStorage:_xmppCapabilitiesCoreDataStorage]; // 2.6 消息归档模块
_xmppMessageArchivingCoreDataStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
_xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_xmppMessageArchivingCoreDataStorage]; // 3. 将重新连接模块添加到XMPPStream
[_xmppReconnect activate:_xmppStream];
[_xmppvCardModule activate:_xmppStream];
[_xmppvCardAvatarModule activate:_xmppStream];
[_xmppRoster activate:_xmppStream];
[_xmppCapabilities activate:_xmppStream];
[_xmppMessageArchiving activate:_xmppStream]; // 4. 添加代理
// 由于所有网络请求都是做基于网络的数据处理,这些数据处理工作与界面UI无关。
// 因此可以让代理方法在其他线城中运行,从而提高程序的运行性能,避免出现应用程序阻塞的情况
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )];
[_xmppRoster addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )];
} // 销毁XMPPStream并注销已注册的扩展模块
- (void)teardownStream
{
// 1. 删除代理
[_xmppStream removeDelegate:self];
[_xmppRoster removeDelegate:self]; // 2. 取消激活在setupStream方法中激活的扩展模块
[_xmppReconnect deactivate];
[_xmppvCardModule deactivate];
[_xmppvCardAvatarModule deactivate];
[_xmppRoster deactivate];
[_xmppCapabilities deactivate];
[_xmppMessageArchiving deactivate]; // 3. 断开XMPPStream的连接
[_xmppStream disconnect]; // 4. 内存清理
_xmppStream = nil;
_xmppReconnect = nil;
_xmppvCardModule = nil;
_xmppvCardAvatarModule = nil;
_xmppvCardStorage = nil;
_xmppRoster = nil;
_xmppRosterStorage = nil;
_xmppCapabilities = nil;
_xmppCapabilitiesCoreDataStorage = nil;
_xmppMessageArchiving = nil;
_xmppMessageArchivingCoreDataStorage = nil;
} // 通知服务器器用户上线
- (void)goOnline
{
// 1. 实例化一个”展现“,上线的报告,默认类型为:available
XMPPPresence *presence = [XMPPPresence presence];
// 2. 发送Presence给服务器
// 服务器知道“我”上线后,只需要通知我的好友,而无需通知我,因此,此方法没有回调
[_xmppStream sendElement:presence];
} // 通知服务器器用户下线
- (void)goOffline
{
// 1. 实例化一个”展现“,下线的报告
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
// 2. 发送Presence给服务器,通知服务器客户端下线
[_xmppStream sendElement:presence];
} // 连接到服务器
- (void)connect
{ // 1. 如果XMPPStream当前已经连接,直接返回
if ([_xmppStream isConnected]) {
return;
}
// 在C语言中if判断真假:非零即真,如果_xmppStream==nil下面这段代码,与上面的代码结果不同。
// if (![_xmppStream isDisconnected]) {
// return;
// } // 2. 指定用户名、主机(服务器),连接时不需要password
NSString *hostName = [[LoginUser sharedLoginUser] hostName];
NSString *userName = [[LoginUser sharedLoginUser] myJIDName]; // 如果没有主机名或用户名(通常第一次运行时会出现),直接显示登录窗口
// if ([hostName isEmptyString] || [userName isEmptyString]) {
// [self showStoryboardWithLogonState:NO];
//
// return;
// } // 3. 设置XMPPStream的JID和主机
[_xmppStream setMyJID:[XMPPJID jidWithString:userName]];
[_xmppStream setHostName:hostName]; // 4. 开始连接
NSError *error = nil;
[_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error]; // 提示:如果没有指定JID和hostName,才会出错,其他都不出错。
if (error) {
NSLog(@"连接请求发送出错 - %@", error.localizedDescription);
} else {
NSLog(@"连接请求发送成功!");
}
} #pragma mark 连接到服务器
- (void)connectWithCompletion:(CompletionBlock)completion failed:(CompletionBlock)faild
{
// 1. 记录块代码
_completionBlock = completion;
_faildBlock = faild; // 2. 如果已经存在连接,先断开连接,然后再次连接
if ([_xmppStream isConnected]) {
[_xmppStream disconnect];
} // 3. 连接到服务器
[self connect];
} // 与服务器断开连接
- (void)disconnect
{
// 1. 通知服务器下线
[self goOffline];
// 2. XMPPStream断开连接
[_xmppStream disconnect];
} - (void)logout
{
// 1. 通知服务器下线,并断开连接
[self disconnect]; // 2. 显示用户登录Storyboard
[self showStoryboardWithLogonState:NO];
} #pragma mark - 代理方法
#pragma mark 连接完成(如果服务器地址不对,就不会调用此方法)
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
// 从系统偏好读取用户密码
NSString *password = [[LoginUser sharedLoginUser] password]; if (_isRegisterUser) {
// 用户注册,发送注册请求
[_xmppStream registerWithPassword:password error:nil];
} else {
// 用户登录,发送身份验证请求
[_xmppStream authenticateWithPassword:password error:nil];
}
} #pragma mark 注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender
{
_isRegisterUser = NO; // 注册成功,直接发送验证身份请求,从而触发后续的操作
[_xmppStream authenticateWithPassword:[LoginUser sharedLoginUser].password error:nil];
} #pragma mark 注册失败(用户名已经存在)
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error
{
_isRegisterUser = NO;
if (_faildBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_faildBlock();
});
}
} #pragma mark 身份验证通过
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
if (_completionBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_completionBlock();
});
} // 通知服务器用户上线
[self goOnline]; // 显示主Storyboard
[self showStoryboardWithLogonState:YES];
} #pragma mark 密码错误,身份验证失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
{
if (_faildBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_faildBlock();
});
} // 显示用户登录Storyboard
[self showStoryboardWithLogonState:NO];
} #pragma mark 用户展现变化
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
NSLog(@"接收到用户展现数据 - %@", presence); // 1. 判断接收到的presence类型是否为subscribe
if ([presence.type isEqualToString:@"subscribe"]) {
// 2. 取出presence中的from的jid
XMPPJID *from = [presence from]; // 3. 接受来自from添加好友的订阅请求
[_xmppRoster acceptPresenceSubscriptionRequestFrom:from andAddToRoster:YES];
}
} #pragma mark 判断IQ是否为SI请求
- (BOOL)isSIRequest:(XMPPIQ *)iq
{
NSXMLElement *si = [iq elementForName:@"si" xmlns:@"http://jabber.org/protocol/si"];
NSString *uuid = [[si attributeForName:@"id"]stringValue]; if(si &&uuid ){
return YES;
} return NO;
} #pragma mark 接收请求
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
NSLog(@"接收到请求 - %@", iq); // 0. 判断IQ是否为SI请求
if ([self isSIRequest:iq]) {
TURNSocket *socket = [[TURNSocket alloc] initWithStream:_xmppStream toJID:iq.to]; [_socketList addObject:socket]; [socket startWithDelegate:self delegateQueue:dispatch_get_main_queue()];
} else if ([TURNSocket isNewStartTURNRequest:iq]) {
// 1. 判断iq的类型是否为新的文件传输请求
// 1) 实例化socket
TURNSocket *socket = [[TURNSocket alloc] initWithStream:sender incomingTURNRequest:iq]; // 2) 使用一个数组成员记录住所有传输文件使用的socket
[_socketList addObject:socket]; // 3)添加代理
[socket startWithDelegate:self delegateQueue:dispatch_get_main_queue()];
} return YES;
} #pragma mark 接收消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
NSLog(@"接收到用户消息 - %@", message); // 1. 针对图像数据单独处理,取出数据
NSString *imageStr = [[message elementForName:@"imageData"] stringValue]; if (imageStr) {
// 2. 解码成图像
NSData *data = [[NSData alloc] initWithBase64EncodedString:imageStr options:NSDataBase64DecodingIgnoreUnknownCharacters]; // 3. 保存图像
UIImage *image = [UIImage imageWithData:data];
// 4. 将图像保存到相册
// 1) target 通常用self
// 2) 保存完图像调用的方法
// 3) 上下文信息
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
} #pragma mark - XMPPRoster代理
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
{
NSLog(@"接收到其他用户的请求 %@", presence);
} #pragma mark - TURNSocket代理
- (void)turnSocket:(TURNSocket *)sender didSucceed:(GCDAsyncSocket *)socket
{
NSLog(@"成功"); // 保存或者发送文件
// 写数据方法,向其他客户端发送文件
// socket writeData:<#(NSData *)#> withTimeout:<#(NSTimeInterval)#> tag:<#(long)#>
// 读数据方法,接收来自其他客户端的文件
// socket readDataToData:<#(NSData *)#> withTimeout:<#(NSTimeInterval)#> tag:<#(long)#> // 读写操作完成之后断开网络连接
[socket disconnectAfterReadingAndWriting]; [_socketList removeObject:sender];
} - (void)turnSocketDidFail:(TURNSocket *)sender
{
NSLog(@"失败"); [_socketList removeObject:sender];
} @end
// LoginViewController.h
// 02.用户登录&注册
// #import <UIKit/UIKit.h> @interface LoginViewController : UIViewController @end
//
// LoginViewController.m
// 02.用户登录&注册
// #import "LoginViewController.h"
#import "NSString+Helper.h"
#import "AppDelegate.h"
#import "LoginUser.h" @interface LoginViewController () <UITextFieldDelegate> @property (weak, nonatomic) IBOutlet UITextField *userNameText;
@property (weak, nonatomic) IBOutlet UITextField *passwordText;
@property (weak, nonatomic) IBOutlet UITextField *hostNameText; @property (weak, nonatomic) IBOutlet UIButton *registerButton;
@property (weak, nonatomic) IBOutlet UIButton *loginButton; @end @implementation LoginViewController #pragma mark - AppDelegate 的助手方法
- (AppDelegate *)appDelegate
{
return [[UIApplication sharedApplication] delegate];
} - (void)viewDidLoad
{
[super viewDidLoad]; // 1. 拉伸按钮背景图片
// 1) 登录按钮
UIImage *loginImage = [UIImage imageNamed:@"LoginGreenBigBtn"];
loginImage = [loginImage stretchableImageWithLeftCapWidth:loginImage.size.width * 0.5 topCapHeight:loginImage.size.height * 0.5];
[_loginButton setBackgroundImage:loginImage forState:UIControlStateNormal]; // 2) 注册按钮
UIImage *registerImage = [UIImage imageNamed:@"LoginwhiteBtn"];
registerImage = [registerImage stretchableImageWithLeftCapWidth:registerImage.size.width * 0.5 topCapHeight:registerImage.size.height * 0.5];
[_registerButton setBackgroundImage:registerImage forState:UIControlStateNormal]; // 2. 设置界面文本的初始值
_userNameText.text = [[LoginUser sharedLoginUser] userName];
_passwordText.text = [[LoginUser sharedLoginUser] password];
_hostNameText.text = [[LoginUser sharedLoginUser] hostName]; // 3. 设置文本焦点
if ([_userNameText.text isEmptyString]) {
[_userNameText becomeFirstResponder];
} else {
[_passwordText becomeFirstResponder];
}
} #pragma mark UITextField代理方法
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == _userNameText) {
[_passwordText becomeFirstResponder];
} else if (textField == _passwordText && [_hostNameText.text isEmptyString]) {
[_hostNameText becomeFirstResponder];
} else {
[self userLoginAndRegister:nil];
} return YES;
} #pragma mark - Actions
#pragma mark 用户登录
- (IBAction)userLoginAndRegister:(UIButton *)button
{
// 1. 检查用户输入是否完整,在商业软件中,处理用户输入时
// 通常会截断字符串前后的空格(密码除外),从而可以最大程度地降低用户输入错误
NSString *userName = [_userNameText.text trimString];
// 用些用户会使用空格做密码,因此密码不能去除空白字符
NSString *password = _passwordText.text;
NSString *hostName = [_hostNameText.text trimString]; if ([userName isEmptyString] ||
[password isEmptyString] ||
[hostName isEmptyString]) { UIAlertView *alter = [[UIAlertView alloc] initWithTitle:@"提示" message:@"登录信息不完整" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil]; [alter show]; return;
} // 2. 将用户登录信息写入系统偏好
[[LoginUser sharedLoginUser] setUserName:userName];
[[LoginUser sharedLoginUser] setPassword:password];
[[LoginUser sharedLoginUser] setHostName:hostName]; // 3. 让AppDelegate开始连接
// 告诉AppDelegate,当前是注册用户
NSString *errorMessage = nil; if (button.tag == ) {
[self appDelegate].isRegisterUser = YES;
errorMessage = @"注册用户失败!";
} else {
errorMessage = @"用户登录失败!";
} [[self appDelegate] connectWithCompletion:nil failed:^{
UIAlertView *alter = [[UIAlertView alloc] initWithTitle:@"提示" message:errorMessage delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil]; [alter show]; if (button.tag == ) {
// 注册用户失败通常是因为用户名重复
[_userNameText becomeFirstResponder];
} else {
// 登录失败通常是密码输入错误
[_passwordText setText:@""];
[_passwordText becomeFirstResponder];
}
}];
} @end
// LoginUser.h #import <Foundation/Foundation.h>
#import "Singleton.h" @interface LoginUser : NSObject
single_interface(LoginUser) @property (strong, nonatomic) NSString *userName;
@property (strong, nonatomic) NSString *password;
@property (strong, nonatomic) NSString *hostName; @property (strong, nonatomic, readonly) NSString *myJIDName; @end
//
// LoginUser.m #import "LoginUser.h"
#import "NSString+Helper.h" #define kXMPPUserNameKey @"admin"
#define kXMPPPasswordKey @"admin"
#define kXMPPHostNameKey @"127.0.0.1" @implementation LoginUser
single_implementation(LoginUser) #pragma mark - 私有方法
- (NSString *)loadStringFromDefaultsWithKey:(NSString *)key
{
NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:key]; return (str) ? str : @"";
} #pragma mark - getter & setter
- (NSString *)userName
{
return [self loadStringFromDefaultsWithKey:kXMPPUserNameKey];
} - (void)setUserName:(NSString *)userName
{
[userName saveToNSDefaultsWithKey:kXMPPUserNameKey];
} - (NSString *)password
{
return [self loadStringFromDefaultsWithKey:kXMPPPasswordKey];
} - (void)setPassword:(NSString *)password
{
[password saveToNSDefaultsWithKey:kXMPPPasswordKey];
} - (NSString *)hostName
{
return [self loadStringFromDefaultsWithKey:kXMPPHostNameKey];
} - (void)setHostName:(NSString *)hostName
{
[hostName saveToNSDefaultsWithKey:kXMPPHostNameKey];
} - (NSString *)myJIDName
{
return [NSString stringWithFormat:@"%@@%@", self.userName, self.hostName];
} @end