XMPP即时通讯(代码实现)
1.配置XMPP(XMPPConfig.m)
2.配置XMPPFramework框架
3.创建单例类(XMPPManager.h/XMPPManager.m)管理器
XMPPManager.m:
#import "XMPPManager.h"
#import "AppDelegate.h"
//连接服务器的目的
typedef NS_ENUM(NSInteger, ConnectToServerPopurpose)
{
ConnectToServerPopurposeLogin, //登录
ConnectToServerPopurposeRegist //注册
};
@interface XMPPManager ()<XMPPStreamDelegate,XMPPRosterDelegate>
@property (nonatomic, assign) ConnectToServerPopurpose serverPurpose; //连接服务器的目的
@property (nonatomic, copy) NSString *loginPassword; //登录密码
@property (nonatomic, copy) NSString *registerPassword; //注册密码
- (void)connectServer; //连接服务器
- (void)disConnectWithServer; //断开服务器
@end
@implementation XMPPManager
static XMPPManager *manager = nil;
+ (XMPPManager *)defaultXMPPManager {
//gcd once 程序执行期间只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManager alloc] init];
});
return manager;
}
//重写init方法
- (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()];
//创建好友列表仓库
XMPPRosterCoreDataStorage *rosterCorDataStorage = [XMPPRosterCoreDataStorage sharedInstance];
//创建花名册对象
self.roster = [[XMPPRoster alloc] initWithRosterStorage:rosterCorDataStorage dispatchQueue:dispatch_get_main_queue()];
//将花名册对象添加到stream活动
[self.roster activate:self.stream];
//添加代理
[self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
//创建信息归档对象
XMPPMessageArchivingCoreDataStorage *messageArchingCoreStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
//创建信息归档对象
self.messageArching = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:messageArchingCoreStorage dispatchQueue:dispatch_get_main_queue()];
//添加活动到通信管道
[self.messageArching activate:self.stream];
//获取数据管理器
self.managerContext = messageArchingCoreStorage.mainThreadManagedObjectContext;
}
return self;
}
#pragma mark - XMPPRosterDelegate
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence {
//获取请求对象的JID
XMPPJID *requestJID = presence.from;
//创建提示框
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"添加好友提醒" message:requestJID.user preferredStyle:UIAlertControllerStyleAlert];
//创建事件
UIAlertAction *addAction = [UIAlertAction actionWithTitle:@"添加" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {
//接受好友请求
[self.roster acceptPresenceSubscriptionRequestFrom:requestJID andAddToRoster:YES];
}];
UIAlertAction *rejectAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//拒绝好友请求
[self.roster rejectPresenceSubscriptionRequestFrom:requestJID];
}];
//添加事件
[alertVC addAction:addAction];
[alertVC addAction:rejectAction];
//弹出提示框
//获取AppDelegate
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
//获取根视图控制器
UIViewController *rootVC = appDelegate.window.rootViewController;
[rootVC presentViewController:alertVC animated:YES completion:nil];
}
//连接服务器
- (void)connectServer {
if ([self.stream isConnected] || [self.stream isConnecting]) {
//断开连接
[self disConnectWithServer];
}
//建立新的连接(30秒超时)
NSError *error = nil;
[self.stream connectWithTimeout:30 error:&error];
if (error) {
NSLog(@"connect fail");
}
}
//登录
- (void)loginWithUserName:(NSString *)username password:(NSString *)pw {
self.loginPassword = pw; //记录登录密码
self.serverPurpose = ConnectToServerPopurposeLogin; //登录标识
//获取jid 唯一标识
XMPPJID *myJID = [XMPPJID jidWithUser:username domain:kDomin resource:kResource];
self.stream.myJID = myJID; //设置JID
//连接服务器
[self connectServer];
}
//注册
- (void)registWithUserName:(NSString *)username passeord:(NSString *)pw {
self.registerPassword = pw; //记录注册密码
self.serverPurpose = ConnectToServerPopurposeRegist; //注册标识
//获取JID唯一标识
XMPPJID *myJID = [XMPPJID jidWithUser:username domain:kDomin resource:kResource];
self.stream.myJID = myJID; //设置JID
//连接服务器
[self connectServer];
}
//添加好友
- (void)addFriend:(NSString *)name {
//创建JID
XMPPJID *myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",name,kHostName]];
//添加好友
[self.roster subscribePresenceToUser:myJID];
}
//删除好友
- (void)deleteFriend:(NSString *)name {
//获取JID
XMPPJID *myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",name,kHostName]];
[self.roster removeUser:myJID];
}
//断开服务器连接
- (void)disConnectWithServer {
//断开服务器
[self.stream disconnect];
}
#pragma mark - XMPPStreamDelegate
//成功连接服务器
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
NSLog(@"connect success");
switch (self.serverPurpose) {
case ConnectToServerPopurposeLogin:
//登录
{
[self.stream authenticateWithPassword:self.loginPassword error:nil];
}
break;
case ConnectToServerPopurposeRegist:
//注册
{
[self.stream registerWithPassword:self.registerPassword error:nil];
}
break;
default:
break;
}
}
//连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
NSLog(@"connect time out");
}
@end
用户登录:
#import "LoginViewController.h"
#import "XMPPManager.h"
#import "RosterTableViewController.h"
BOOL isClickButton = YES;
@interface LoginViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userNameTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@end
@implementation LoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
//添加代理
[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#pragma mark - handleAction
- (IBAction)handleLogin:(UIButton *)sender {
isClickButton = YES; //标识点击了登录button
[[XMPPManager defaultXMPPManager] loginWithUserName:self.userNameTF.text password:self.passwordTF.text];
}
- (IBAction)handleRegister:(UIButton *)sender {
}
#pragma mark - XMPPStreamDelegate
//登录成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
//上线--更改状态
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
[[XMPPManager defaultXMPPManager].stream sendElement:presence];
if (isClickButton) {
//提示
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"欢迎回来" preferredStyle:(UIAlertControllerStyleAlert)];
//添加事件
UIAlertAction *action = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {
//获取从storyBoard中获取联系人列表界面
RosterTableViewController *rosterVC = [self.storyboard instantiateViewControllerWithIdentifier:@"contact"];
//传值
rosterVC.userName = self.userNameTF.text;
rosterVC.paassWord = self.passwordTF.text;
//push
[self.navigationController pushViewController:rosterVC animated:YES];
}];
[alertVC addAction:action];
//弹出提示
[self presentViewController:alertVC animated:YES completion:nil];
//更改BOOL
isClickButton = NO;
}
}
//登录失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error {
//提示
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"账号或密码错误请核对" preferredStyle:(UIAlertControllerStyleAlert)];
//添加事件
UIAlertAction *action = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {
}];
[alertVC addAction:action];
//弹出提示
[self presentViewController:alertVC animated:YES completion:nil];
}
@end
用户注册:
#import "RegisterViewController.h"
#import "XMPPManager.h"
@interface RegisterViewController ()<XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userNameTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@property (weak, nonatomic) IBOutlet UITextField *rePasswordTF;
@end
@implementation RegisterViewController
- (void)viewDidLoad {
[super viewDidLoad];
//添加代理
[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#pragma mark - handleAction
- (IBAction)handleSubmit:(UIButton *)sender {
//注册
[[XMPPManager defaultXMPPManager] registWithUserName:self.userNameTF.text passeord:self.passwordTF.text];
}
- (IBAction)handleCleare:(UIButton *)sender {
}
#pragma mark - XMPPStreamDelegate
//注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender {
NSLog(@"register success");
}
//注册失败
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error {
NSLog(@"register fail");
}
@end
联系人列表
#import "RosterTableViewController.h"
#import "RosterCell.h"
#import "ChatTableViewController.h"
#import "XMPPManager.h"
@interface RosterTableViewController ()<XMPPRosterDelegate>
@property (nonatomic, strong) NSMutableArray *contacts; //联系人数组
@end
@implementation RosterTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.contacts = [NSMutableArray array]; //创建数组
//添加代理
[[XMPPManager defaultXMPPManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
//再次登录
[[XMPPManager defaultXMPPManager] loginWithUserName:self.userName password:self.paassWord];
}
- (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.contacts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RosterCell *cell = [tableView dequeueReusableCellWithIdentifier:@"roster" forIndexPath:indexPath];
//获取对应的联系人JID
XMPPJID *jid = self.contacts[indexPath.row];
cell.textLabel.text = jid.user;
return cell;
}
#pragma mark - XMPPRosterDelegate
//开始检索好友
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender {
NSLog(@"begin search friends");
}
//检索好友,每执行一次获取一个好友信息
- (void)xmppRoster:(XMPPRoster *)sender didRecieveRosterItem:(NSXMLElement *)item {
NSLog(@"%@",[[item attributeForName:@"jid"] stringValue]);
//获取JIDStr
NSString *jidStr = [[item attributeForName:@"jid"] stringValue];
//获取JID
XMPPJID *myJID = [XMPPJID jidWithString:jidStr resource:kResource];
//防止重复添加好友
for (XMPPJID *JID in self.contacts) {
if ([JID.user isEqualToString:myJID.user]) {
return;
}
}
//放入数组
[self.contacts addObject:myJID];
//刷新界面
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
}
//结束检索好友
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender {
NSLog(@"end search friends");
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//界面传值
//获取下一个试图控制器
ChatTableViewController *chatVC = segue.destinationViewController;
//获取cell
UITableViewCell *cell = sender;
//获取下标
NSInteger index = [self.tableView indexPathForCell:cell].row;
//获取对应的对象
XMPPJID *JID = self.contacts[index];
chatVC.friendJID = JID;
}
@end
聊天控制器
#import "ChatTableViewController.h"
#import "ChatCell.h"
@interface ChatTableViewController ()<XMPPStreamDelegate>
@property (nonatomic, strong) NSMutableArray *messageArray; //用来存储信息
@end
@implementation ChatTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"发送" style:(UIBarButtonItemStyleDone) target:self action:@selector(sendMessage)];
//添加代理
[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//创建数组
self.messageArray = [NSMutableArray array];
//获取本地聊天信息
[self reloadMessage];
}
//发送新的消息
- (void)sendMessage
{
//创建新的信息
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.friendJID];
//添加信息体
[message addBody:@"比如说"];
[[XMPPManager defaultXMPPManager].stream sendElement:message];
}
- (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.messageArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ChatCell *cell = [tableView dequeueReusableCellWithIdentifier:@"chat" forIndexPath:indexPath];
//获取对应数组元素
XMPPMessage *message = self.messageArray[indexPath.row];
if ([message isKindOfClass:[XMPPMessage class]]) {
//将收到的信息放到左边,发送的放到右边
if ([message.from.user isEqualToString:self.friendJID.user]) {
cell.textLabel.text = message.body;
cell.detailTextLabel.text = @"";
}else {
cell.textLabel.text = @"";
cell.detailTextLabel.text = message.body;
}
}else {
//数组中的对象时XMPPMessageArchiving_Mesage_CoreDataObject类型
XMPPMessageArchiving_Message_CoreDataObject *mes = (XMPPMessageArchiving_Message_CoreDataObject *)message;
//Outgoing发送,用来判断消息是接受的 还是发送的
if (![mes isOutgoing]) {
cell.textLabel.text = mes.message.body;
cell.detailTextLabel.text = @"";
}else {
cell.textLabel.text = @"";
cell.detailTextLabel.text = mes.message.body;
}
}
return cell;
}
//读取本地信息
- (void)reloadMessage
{
//获取数据管理器
NSManagedObjectContext *managerContext = [XMPPManager defaultXMPPManager].managerContext;
//请求对象
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//实体描述对象
//XMPPMessageArchiving_Message_CoreDataObject是持久化信息对应的实体类
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:managerContext];
[fetchRequest setEntity:entity];
// 查询条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ AND streamBareJidStr == %@", self.friendJID.bare,[XMPPManager defaultXMPPManager].stream.myJID.bare];
[fetchRequest setPredicate:predicate];
// 排序
//按照时间排序
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
//执行查询,获取符合条件的对象
NSArray *fetchedObjects = [managerContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"your content is null for search");
}
//将查询到的本地聊天信息存放到数组中
[self.messageArray addObjectsFromArray:fetchedObjects];
//刷新数据
[self.tableView reloadData];
}
//展示信息
- (void)showMessageWithMessage:(XMPPMessage *)message {
//将信息放入数组
[self.messageArray addObject:message];
//刷新数据
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messageArray.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
//滑动tableView到对应的cell
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:(UITableViewScrollPositionBottom) animated:YES];
}
#pragma mark - XMPPSteamDelegate
//接收信息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
NSLog(@"%@",message.body);
//只获取当前好友的聊天信息
if ([message.from.user isEqualToString:self.friendJID.user]) {
//展示信息
[self showMessageWithMessage:message];
}
}
//发送信息
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message {
[self showMessageWithMessage:message];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end