前面部分呢,我們實現了一個XMPP本地服務器的搭建 http://www.lxweimin.com/p/5ea6b611c5b1 但是我們最終的目的并不是這些,我們是要實現即時通訊的功能,今天我們就在代碼里面實現即時通訊的基本功能。
一、項目環境配置
首先我們在工程中導入XMPPFramework
添加本地依賴庫:libxml2.tbd和libresolv.tbd
然后Build Settings->Header searches Pathes添加字段:/usr/include/libxml2
Commend+B編譯正常的話就OK了
二、建立與服務器的鏈接
我們在建立與服務器鏈接之前需要自己創建:登陸頁面,注冊頁面,好友列表頁面和聊天頁面,這些在此不再累述,想必大家用可視化,分分鐘搞定。至于各個頁面之間的關聯,更是小kiss
我們需要建立一個XMPP的任務管理類對象 而且這個對象只需要一個 我們可以用單例:
.h
#import <Foundation/Foundation.h>
#import "XMPPFramework.h"
@interface XMPPManager : NSObject<XMPPStreamDelegate>
+(XMPPManager *)sharedXMPPManager;//創建管理者對象單例
@property(nonatomic,strong)XMPPStream *stream;//信息管道
@property(nonatomic,strong)XMPPRoster *xmppRoster;//進行添加好友 刪除好友 獲取還有列表等功能
@property(nonatomic,strong)XMPPMessageArchiving *xmppMessageArchivering;
@property(nonatomic,strong)NSManagedObjectContext *messageManagerContext;//聊天信息托管對象 上下文
//登陸
-(void)loginWithUser:(NSString *)user password:(NSString *)password;
//注冊
-(void)registWithUser:(NSString *)user password:(NSString *)password;
@end
.m
#import "XMPPManager.h"
//判斷想服務器建立連接是登陸還是注冊
typedef enum : NSUInteger {
ConnectToServerPurposeLogin,//登陸
ConnectToServerPurposeRegist,//注冊
} MyStatus;
@interface XMPPManager ()
@property(nonatomic,strong)NSString *password;//登錄密碼
@property(nonatomic,strong)NSString *registPassword;//注冊密碼
@property(nonatomic,assign)MyStatus connectToServerPurse;
@end
@implementation XMPPManager
+(XMPPManager *)sharedXMPPManager{
static XMPPManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManager alloc]init];
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.stream = [[XMPPStream alloc]init];
self.stream.hostName = kHostName;//服務器的地址
self.stream.hostPort = kHostPort;//端口號標識唯一的服務
[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//讓當前對象成為stream的代理
//進行好友存儲
XMPPRosterCoreDataStorage *storage= [XMPPRosterCoreDataStorage sharedInstance];
self.xmppRoster = [[XMPPRoster alloc]initWithRosterStorage:storage dispatchQueue:dispatch_get_main_queue()];
[self.xmppRoster activate:self.stream];//激活
//進行聊天信息存儲
XMPPMessageArchivingCoreDataStorage *archiveringStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.xmppMessageArchivering = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:archiveringStorage dispatchQueue:dispatch_get_main_queue()];
[self.xmppMessageArchivering activate:self.stream];//激活
self.messageManagerContext = archiveringStorage.mainThreadManagedObjectContext;
}
return self;
}
//與服務器建立鏈接
-(void)connectToServerWithUser:(NSString *)user{
//要是正在鏈接的話那么就先斷開連接
if ([self.stream isConnected]) {
[self disconnectServer];
}
XMPPJID *jid = [XMPPJID jidWithUser:user domain:kDomin resource:kResource];
self.stream.myJID = jid;
NSError *error = nil;
[self.stream connectWithTimeout:30.0f error:&error];
if (nil != error) {
NSLog(@"%s__%d__鏈接出錯:%@",__FUNCTION__,__LINE__,error);
}
}
//與服務器斷開鏈接
-(void)disconnectServer{
[self.stream disconnect];
}
//登陸
-(void)loginWithUser:(NSString *)user password:(NSString *)password{
self.connectToServerPurse = ConnectToServerPurposeLogin;
self.password = password;//將傳進來的password傳給self.password
[self connectToServerWithUser:user];
}
//注冊
-(void)registWithUser:(NSString *)user password:(NSString *)password{
self.connectToServerPurse = ConnectToServerPurposeRegist;
self.registPassword = password;
[self connectToServerWithUser:user];
}
#pragma mark--XMPPStreamDelegate
//我們一旦想服務器發送請求之后又兩種結果 成功和失敗 我們需要實現XMPP協議的兩個方法
//與服務器鏈接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
#pragma mark ------判斷與服務器建立連接是登陸還是注冊
switch (self.connectToServerPurse) {
case ConnectToServerPurposeLogin:
{
//建立連接成功之后需要向服務器驗證自己的身份
NSError *error = nil;
[self.stream authenticateWithPassword:self.password error:&error];
if (nil != error) {
NSLog(@"%s__%d__驗證出錯:%@",__FUNCTION__,__LINE__,error);
}
break;
}
case ConnectToServerPurposeRegist:
{
NSError *err = nil;
[self.stream registerWithPassword:self.registPassword error:&err];
if (nil != err) {
NSLog(@"%s__%d__注冊出錯:%@",__FUNCTION__,__LINE__,err);
}
break;
}
default:
break;
}
}
//與服務器鏈接失敗
-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}
@end
登陸
我們在登陸頁面點擊登陸按鈕的時候調用XMPPManager的-(void)loginWithUser:(NSString *)user password:(NSString *)password;進行登陸,但是我們在進行登陸的時候是需要服務器的驗證的所有這些都直接在代碼理顯示了,如下:
#import "LoginViewController.h"
#import "XMPPManager.h"
@interface LoginViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *userPassword;
@end
@implementation LoginViewController
- (IBAction)loginButton:(id)sender {
//獲取用戶名和密碼
NSString *userName = self.userName.text;
NSString *userPassword = self.userPassword.text;
[[XMPPManager sharedXMPPManager]loginWithUser:userName password:userPassword];
}
- (void)viewDidLoad {
[super viewDidLoad];
[[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
// Do any additional setup after loading the view.
}
#pragma mark--XMPPStreamDelegate 進行身份驗證
//驗證成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
NSLog(@"%s__%d__驗證成功",__FUNCTION__,__LINE__);
[[NSUserDefaults standardUserDefaults]setObject:self.userName.text forKey:@"userName"];
[[NSUserDefaults standardUserDefaults]setObject:self.userPassword.text forKey:@"userPassword"];
[[NSUserDefaults standardUserDefaults]synchronize];//立即存儲
[self dismissViewControllerAnimated:YES completion:nil];
}
//驗證失敗
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
NSLog(@"%s__%d__驗證出錯:%@",__FUNCTION__,__LINE__,error);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
此時我們也需要在AppDelegate里面作如下處理,當我們再次打開應用程序的時候要是已經驗證成功就顯示好友列表頁面,否則的話就顯示登陸注冊頁面,由于清一色的代碼 給大家來張圖緩解一下緊張的氣氛吧。
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (nil == [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"]) {
UIStoryboard *story = [UIStoryboard storyboardWithName:@"LoginAndPregist" bundle:nil];
UINavigationController *nv = [story instantiateInitialViewController];
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:nv animated:YES completion:^{
}];
}else{
NSString *userName = [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"];
NSString *password = [[NSUserDefaults standardUserDefaults]objectForKey:@"userPassword"];
[[XMPPManager sharedXMPPManager]loginWithUser:userName password:password];
[[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return YES;
}
#pragma mark--XMPPStreamDelegate
//驗證成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
//我們驗證之后默認的狀態是離線的 于是我們想顯示在線,需要告訴服務器自己的狀態
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
[[XMPPManager sharedXMPPManager].stream sendElement:presence];
}
//驗證失敗
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
}
注冊
其實注冊和登陸是有很大的相似性的,例如同樣需要遵守XMPPStreamDelegate協議,我們點擊注冊按鈕的時候是會執行XMPPManager的-(void)registWithUser:(NSString *)user password:(NSString *)password;進行注冊
一樣的注冊成功還是失敗都會有相應的方法來執行,
#import "RegistViewController.h"
#import "XMPPManager.h"
@interface RegistViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *userPassword;
@end
@implementation RegistViewController
- (IBAction)registButton:(id)sender {
NSString *userName =self.userName.text;
NSString *userpassword = self.userPassword.text;
[[XMPPManager sharedXMPPManager]registWithUser:userName password:userpassword];
}
- (void)viewDidLoad {
[super viewDidLoad];
//設置代理
[[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark--XMPPStreamDelegate
//注冊成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender{
NSLog(@"%s__%d__注冊成功",__FUNCTION__,__LINE__);
//注冊成功之后返回到登陸界面
[self.navigationController popToRootViewControllerAnimated:YES];
}
//注冊失敗
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
NSLog(@"%s__%d__注冊失敗%@",__FUNCTION__,__LINE__,error);
}
@end
獲取好友列表
獲取還有列表我們需要在XMPPManager里面創建一個XMPPRoster對象
這個對象具有添加好友 刪除好友 獲取好友列表等功能
@property(nonatomic,strong)XMPPRoster *xmppRoster;
具體的代碼部分 我寫的非常詳細 大家接著看
#import "RosterTableViewController.h"
#import "XMPPManager.h"
#import "ChatTableViewController.h"
@interface RosterTableViewController ()<XMPPRosterDelegate>
@property(nonatomic,strong)NSMutableArray *rosterArray;//保存獲取的好友
@end
@implementation RosterTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化數組 這個數組里面保存的是獲取的好友
self.rosterArray = [NSMutableArray arrayWithCapacity:10];
[[XMPPManager sharedXMPPManager].xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.rosterArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
XMPPJID *jid = self.rosterArray[indexPath.row];
cell.textLabel.text = jid.user;
return cell;
}
#pragma mark--XMPPRosterDelegate
//剛開始獲取好友列表
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}
//正在獲取好友列表
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{
NSLog(@"%s__%d__Item=%@",__FUNCTION__,__LINE__,item);
NSString *jidStr = [[item attributeForName:@"jid"]stringValue];
XMPPJID *jid =[XMPPJID jidWithString:jidStr];
[self.rosterArray addObject:jid];
//將數據添加進數組
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.rosterArray.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
}
//已經完成獲取好友列表
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
}
#pragma mark--這個方法的作用是傳遞一個值到我們需要點擊進入的聊天頁面,同時在聊天頁面設置一個屬性值來接收
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UITableViewCell *cell = (UITableViewCell *)sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
XMPPJID *jid = self.rosterArray[indexPath.row];
ChatTableViewController *chat = segue.destinationViewController;
chat.chatToJid = jid;
}
@end
聊天頁面
我們在聊天頁面開了一個接口,來接受列表頁面傳進來的XMPPJID
#import <UIKit/UIKit.h>
#import "XMPPManager.h"
@interface ChatTableViewController : UITableViewController
@property(nonatomic,strong)XMPPJID *chatToJid;//要聊天的好友
@end
首先,在ChatTableViewController.m里面 我們同樣的遵守代理協議XMPPStreamDelegate,定義一個可變數組來接收查詢的所有聊天信息
@implementation ChatTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.allMessageArray = [NSMutableArray array];//初始化數組
[[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//設置監測對象
[self reloadMessage];
}
//點擊右上角添加按鈕傳遞一條信息
- (IBAction)addButton:(id)sender {
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJid];
[message addBody:@"Hello"];
[[XMPPManager sharedXMPPManager].stream sendElement:message];
}
以下是XMPPStreamDelegate對于信息發送的幾個方法
#pragma mark--XMPPStreamDelegate
//發送信息成功
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
NSLog(@"%s__%d__",__FUNCTION__,__LINE__);
[self reloadMessage];//發送信息是 刷新一次頁面
}
//發送信息失敗
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error{
NSLog(@"%s__%d__Error:%@",__FUNCTION__,__LINE__,error);
}
//收到信息
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
NSLog(@"%s__%d__Message=%@",__FUNCTION__,__LINE__,message);
[self reloadMessage];//收到信息是刷新一次頁面
}
我們在發送消失 或者接收到消失 以及我們進入頁面的時候都會顯示相應的內容 這其實就設計頁面數據加載的內容,因此我們自定義一個方法,用來加載聊天頁面的內容,并在相應的方法里進行調用
#pragma mark--加載聊天信息
-(void)reloadMessage{
NSManagedObjectContext *managerObjectContext =[XMPPManager sharedXMPPManager].messageManagerContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:managerObjectContext];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
//我們要是不設置相應的檢索條件的話 就會吧所有的聊天信息檢索出來出來
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ And streamBareJidStr == %@", self.chatToJid.bare,[XMPPManager sharedXMPPManager].stream.myJID.bare];//對方的Jid和自己的Jid
[fetchRequest setPredicate:predicate];
//以上查詢出來的結果就是當前用戶和聊天用戶之間的信息
// Specify how the fetched objects should be sorted
//設置
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchedObjects = [managerObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"沒有檢索出任何內容");
}else{
//在此判斷一下,因為日要是點擊一個人,沒有聊天記錄的話直接return 否則會崩掉的
if (fetchedObjects.count == 0) {
return;
}
[self.allMessageArray removeAllObjects];
[self.allMessageArray addObjectsFromArray:fetchedObjects];
[self.tableView reloadData];
//進行信息加載的時候讓tableView 滾動到最底部
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.allMessageArray.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
至于UITableView data source的幾個代理方法就簡單的不行不行的了。。。
Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allMessageArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Message" forIndexPath:indexPath];
XMPPMessageArchiving_Message_CoreDataObject *message = self.allMessageArray[indexPath.row];
//判斷聊天信息是發出去的 還是接受進來的,在cell上顯示不一樣的樣式
if (message.isOutgoing == YES) {
cell.detailTextLabel.text = message.body;
cell.textLabel.text = @"";
}else{
cell.textLabel.text = message.body;
cell.detailTextLabel.text = @"";
}
return cell;
}
以上就是XMPP實現聊天功能的基本內容,我們在以代碼為伍的時候 可以用Openfile和spark等工具幫助我們進行調試~~~~~