在前两节中,我们构建了应用程序的框架并将XMPP Framework添加到了工程中,但这仅仅还是个空的应用程序,这一节我们会建立XMPP链接并登录到Openfire服务器中。
1. 实现XMPPFramework
1.1 添加接口和属性
修改YDAppDelegate.h,添加对Core Data和XMPPFramework.h的引用
#import <CoreData/CoreData.h>
#import "XMPPFramework.h"
添加XMPP相关的属性和接口声明
@interface YDAppDelegate : UIResponder <UIApplicationDelegate,YDLeftMenuViewControllerDelegate,YDSlideMenuContainerViewControllerDelegate>
{
BOOL allowSelfSignedCertificates;
BOOL allowSSLHostNameMismatch;
BOOL isXmppConnected;
}
//XMPP
@property (nonatomic, strong, readonly) XMPPStream *xmppStream;
@property (nonatomic, strong, readonly) XMPPReconnect *xmppReconnect;
@property (nonatomic, strong, readonly) XMPPRoster *xmppRoster;
@property (nonatomic, strong, readonly) XMPPRosterCoreDataStorage *xmppRosterStorage;
@property (nonatomic, strong, readonly) XMPPvCardTempModule *xmppvCardTempModule;
@property (nonatomic, strong, readonly) XMPPvCardAvatarModule *xmppvCardAvatarModule;
@property (nonatomic, strong, readonly) XMPPvCardCoreDataStorage *xmppvCardStorage;
@property (nonatomic, strong, readonly) XMPPCapabilities *xmppCapabilities;
@property (nonatomic, strong, readonly) XMPPCapabilitiesCoreDataStorage *xmppCapabilitiesStorage;
- (NSManagedObjectContext *)managedObjectContext_roster;
- (NSManagedObjectContext *)managedObjectContext_capabilities;
//public methods
- (BOOL)connect;
- (void)disconnect;
1.2 引入Keychain
iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储。相对于NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因App被删除而丢失,所以在重装App后,keychain里的数据还能使用。从ios 3。0开始,跨程序分享keychain变得可行。
如何需要在应用里使用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。
从apple官网,可以下载KeychainItemWrapper:
KeychainItemWrapper是apple官方例子“GenericKeychain”里一个访问keychain常用操作的封装类,在官网上下载了GenericKeychain项目后,只需要把“KeychainItemWrapper.h”和“KeychainItemWrapper.m”拷贝到我们项目,并导入Security.framework 。KeychainItemWrapper的用法:
/** 初始化一个保存用户帐号的KeychainItemWrapper */
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Account Number"
accessGroup:@"YOUR_APP_ID_HERE.com.yourcompany.AppIdentifier"];
//保存帐号
[wrapper setObject:@"<帐号>" forKey:(id)kSecAttrAccount];
//保存密码
[wrapper setObject:@"<帐号密码>" forKey:(id)kSecValueData];
//从keychain里取出帐号密码
NSString *password = [wrapper objectForKey:(id)kSecValueData];
其中方法“- (void)setObject:(id)inObject forKey:(id)key;”里参数“forKey”的值应该是Security.framework 里头文件“SecItem.h”里定义好的key,用其他字符串做key程序会崩溃!
此外,由于我们的工程使用了ARC,但是KeyChainItemWrapper不支持ARC,我们需要修改工程配置,告诉编译器这个类与ARC不兼容。-fno-objc-arc
2. 实现登陆
登陆界面由两个UITextField和两个UIButton组成。用于输入用户名和密码以及进行登陆和取消操作。
首先实现YDSigninViewController.h,声明一个代理协议,用于通知代理证书已被存储事件。
#import <UIKit/UIKit.h>
@protocol YDSignInViewControllerDelegate <NSObject>
-(void)credentialsStored;
@end
@interface YDSignInViewController : UIViewController
@property (nonatomic, strong) id<YDSignInViewControllerDelegate> delegate;
@end
接下来实现YDSigninViewController.m
#import <QuartzCore/QuartzCore.h>
#import "YDSignInViewController.h"
#import "KeychainItemWrapper.h"
@interface YDSignInViewController ()
@property (nonatomic,strong) UITextField *jidField;
@property (nonatomic,strong) UITextField *passwordField;
@end
@implementation YDSignInViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton=YES;
// Do any additional setup after loading the view.
self.view.backgroundColor=[UIColor whiteColor];
self.title = @"Sign in";
self.jidField = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 80.0, 280, 25.0)];
self.jidField.borderStyle = UITextBorderStyleNone;
self.jidField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
self.jidField.tag = 1;
self.jidField.keyboardType = UIKeyboardTypeDefault;
self.jidField.backgroundColor = [UIColor whiteColor];
self.jidField.font = [UIFont systemFontOfSize:14.0];
self.jidField.autocorrectionType = UITextAutocorrectionTypeNo;
self.jidField.placeholder = @"Enter username";
self.jidField.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.jidField.returnKeyType = UIReturnKeyNext;
self.jidField.borderStyle = UITextBorderStyleRoundedRect;
self.jidField.layer.borderWidth = 1.0f;
self.jidField.layer.borderColor = [[UIColor grayColor] CGColor];
self.jidField.layer.cornerRadius = 5.0f;
[self.view addSubview:self.jidField ];
self.passwordField = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 120.0, 280, 25.0)];
self.passwordField.borderStyle = UITextBorderStyleNone;
self.passwordField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
self.passwordField.tag = 2;
self.passwordField.keyboardType = UIKeyboardTypeDefault;
self.passwordField.backgroundColor = [UIColor whiteColor];
self.passwordField.font = [UIFont systemFontOfSize:14.0];
self.passwordField.autocorrectionType = UITextAutocorrectionTypeNo;
self.passwordField.placeholder = @"Enter the password";
self.passwordField.secureTextEntry = YES;
self.passwordField.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.passwordField.returnKeyType = UIReturnKeyNext;
self.passwordField.borderStyle = UITextBorderStyleRoundedRect;
self.passwordField.layer.borderWidth = 1.0f;
self.passwordField.layer.borderColor = [[UIColor grayColor] CGColor];
self.passwordField.layer.cornerRadius = 5.0f;
[self.view addSubview:self.passwordField ];
UIButton* signInButton = [[UIButton alloc] initWithFrame:CGRectMake(20,160,280,25)];
signInButton.backgroundColor=[UIColor blueColor];
signInButton.layer.borderWidth = 1.0f;
signInButton.layer.borderColor = [[UIColor grayColor] CGColor];
signInButton.layer.cornerRadius = 5.0f;
[signInButton addTarget:self action:@selector(saveCredentials:) forControlEvents:UIControlEventTouchUpInside];
UILabel *signInLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,280,25)];
signInLabel.backgroundColor=[UIColor clearColor];
[signInLabel setFont:[UIFont systemFontOfSize:16]];
signInLabel.text=@"Sign in";
signInLabel.adjustsFontSizeToFitWidth=YES;
signInLabel.textAlignment=NSTextAlignmentCenter;
signInLabel.textColor=[UIColor whiteColor];
[signInButton addSubview:signInLabel];
[self.view addSubview:signInButton];
//Cancel
UIButton* cancelButton = [[UIButton alloc] initWithFrame:CGRectMake(20,200,280,25)];
cancelButton.backgroundColor=[UIColor blueColor];
cancelButton.layer.borderWidth = 1.0f;
cancelButton.layer.borderColor = [[UIColor grayColor] CGColor];
cancelButton.layer.cornerRadius = 5.0f;
[cancelButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside];
UILabel *cancelLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,280,25)];
cancelLabel.backgroundColor=[UIColor clearColor];
[cancelLabel setFont:[UIFont systemFontOfSize:16]];
cancelLabel.text=@"Cancel";
cancelLabel.adjustsFontSizeToFitWidth=YES;
cancelLabel.textAlignment=NSTextAlignmentCenter;
cancelLabel.textColor=[UIColor whiteColor];
[cancelButton addSubview:cancelLabel];
[self.view addSubview:cancelButton];
}
-(IBAction)saveCredentials:(UIButton *)sender
{
if (([self.jidField.text length] == 0) ||([self.passwordField.text length] == 0) )
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops"
message:@"Both fields are mandatory"
delegate:self cancelButtonTitle:@"OK"
otherButtonTitles:nil, nil];
[alert show];
return;
}
else
{
KeychainItemWrapper* keychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"YDCHAT" accessGroup:nil];
NSString *jid = [NSString stringWithFormat:@"%@@%@",self.jidField.text,kXMPPServer];
[[NSUserDefaults standardUserDefaults] setValue:jid forKey:kXMPPmyJID];
[[NSUserDefaults standardUserDefaults] synchronize];
[keychain setObject:self.passwordField.text forKey:(__bridge id)kSecValueData];
[self.navigationController popToRootViewControllerAnimated:NO];
[self.delegate credentialsStored];
}
}
-(IBAction)cancel:(UIButton *)sender
{
[self.navigationController popToRootViewControllerAnimated:NO];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
完成了YDSigninViewController后,我们修改YDAppDelegate,实现credentialsStored协议,当用户输入用户名和密码后,调用connect方法连接openfire服务器。
-(void)credentialsStored
{
if (![self connect])
{
DDLogInfo(@"credentialsStored self connect failed");
}
}
连接服务器方法如下,读取储存的用户名和密码调用connectWithTimeout方法连接服务器:
- (BOOL)connect
{
if (![self.xmppStream isDisconnected]) {
return YES;
}
NSString *myJID = [[NSUserDefaults standardUserDefaults] stringForKey:kXMPPmyJID];
KeychainItemWrapper* keychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"YDCHAT" accessGroup:nil];
NSString *myPassword = [keychain objectForKey:(__bridge id)kSecValueData];
userPassword = myPassword;
//
// If you don't want to use the Settings view to set the JID,
// uncomment the section below to hard code a JID and password.
//
// myJID = @"user@gmail.com/xmppframework";
// myPassword = @"";
if (myJID == nil || myPassword == nil) {
return NO;
}
[self.xmppStream setMyJID:[XMPPJID jidWithString:myJID]];
NSError *error = nil;
if (![self.xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error])
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error connecting"
message:@"See console for error details."
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alertView show];
DDLogError(@"Error connecting: %@", error);
return NO;
}
return YES;
}
认证通过后,xmppStreamDidAuthenticate被调用,在其中调用goOnline方法:
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
[self goOnline];
}
goOnline方法中,向服务器发送上线消息:
// It's easy to create XML elments to send and to read received XML elements.
// You have the entire NSXMLElement and NSXMLNode API's.
//
// In addition to this, the NSXMLElement+XMPP category provides some very handy methods for working with XMPP.
//
// On the iPhone, Apple chose not to include the full NSXML suite.
// No problem - we use the KissXML library as a drop in replacement.
//
// For more information on working with XML elements, see the Wiki article:
// https://github.com/robbiehanson/XMPPFramework/wiki/WorkingWithElements
- (void)goOnline
{
XMPPPresence *presence = [XMPPPresence presence]; // type="available" is implicit
NSString *domain = [self.xmppStream.myJID domain];
//Google set their presence priority to 24, so we do the same to be compatible.
if([domain isEqualToString:@"gmail.com"]
|| [domain isEqualToString:@"gtalk.com"]
|| [domain isEqualToString:@"talk.google.com"])
{
NSXMLElement *priority = [NSXMLElement elementWithName:@"priority" stringValue:@"24"];
[presence addChild:priority];
}
[[self xmppStream] sendElement:presence];
[self.rootViewController updateStatus:@"Online"];
}
程序执行效果如下:左侧是模拟器上的显示,我们已经在线了,又侧是adium上的效果。