XMPP核心的几个类 :XMPPStream(通讯管道)、XMPPJID(用户标示)、XMPPPresence(出席通知)、XMPPRoster(花名册)
一.登录注册
1.创建单例(XMPPManager)
static XMPPManager *manager = nil;
+ (XMPPManager *)sharedManager{
// GCD 创建单例对象
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManager alloc] init];
});
return manager;
}
2.配置通讯管道
- (instancetype)init{
self = [super init];
if (self) {
//-----------------配置通信管道---------------
self.stream = [[XMPPStream alloc] init];
// 设置通信管道的目标服务器地址
_stream.hostName = kHostName;
// 设置通信管道的xmpp server端口
_stream.hostPort = kHostPort;
// 设置代理
[_stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return self;
}
3.构造XMPPJID
// 根据一个用户名构造一个xmppjid
XMPPJID *myjid = [XMPPJID jidWithUser:userName domain:kDomin resource:kResource];
// 设置通信管道的jid
_stream.myJID = myjid;
4.连接服务器
if ([_stream isConnected]) {
NSLog(@"已经连接");
[_stream disconnect]; // 断开连接
}
BOOL result = [_stream connectWithTimeout:30 error:nil];
if (result){
NSLog(@"服务器链接成功");
}else{
NSLog(@"服务器链接失败");
}
5.连接服务器常用代理方法
// 服务器连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
NSLog(@"服务器连接超时");
}
// 断开连接
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
if (error) {
NSLog(@"%@",error);
}
// 离线消息(下线通知)
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
// 发送下线通知
[_stream sendElement:presence];
}
6.登陆 先连接服务器,再登陆
// 连接成功之后发起登陆事件
[_stream authenticateWithPassword:self.loginPassword error:nil];
7.登陆常用代理方法
// 登陆成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
NSLog(@"登陆成功");
// 出席消息(上线通知)
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
// 发送上线通知
[_stream sendElement:presence];
}
// 登陆失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
NSLog(@"登陆失败:%@",error);
}
8.注册 先连接服务器,再注册
// 连接成功之后发起注册事件
[_stream registerWithPassword:self.regPassword error:nil];
9.注册常用的代理方法
// 注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender{
NSLog(@"注册成功");
}
// 注册失败
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
NSLog(@"注册失败, error:%@",error);
}
二. 好友列表、添加好友、发送消息、接收消息
1.单例中创建花名册的属性 (XMPPRoster 可以处理和好友相关的事:获取好友列表,添加好友,接收好友请求,同意添加好友,拒绝添加好友)
/**
* 好友花名册,用来处理和好友相关的事件
*/
@property(nonatomic, strong) XMPPRoster * roster;
2.初始化一个花名册,并且在通讯管道中激活花名册
//------------------用户花名册----------------
// xmpp为我们提供了一个CoreData存储器
XMPPRosterCoreDataStorage *xrcds = [XMPPRosterCoreDataStorage sharedInstance];
// 创建roster 花名册时,需要给花名册指定一个数据存储的地方(就是XMPPRosterCoreDataStorage)
self.roster = [[XMPPRoster alloc] initWithRosterStorage:xrcds dispatchQueue:dispatch_get_main_queue()];
// 在通讯管道中激活花名册
// 这时就可以通过通讯管道去给服务器发送请求了。
// 然后roster的消息都通过stream间接的发给服务器
[self.roster activate:self.stream];
3.设置花名册代理,在好友列表控制器里面设置,列表控制器遵循代理 XMPPRosterDelegate// 设置代理
[[XMPPManager sharedManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
4.声明一个数组存放好友
@property (nonatomic, strong) NSMutableArray *rosters;
5.花名册常用代理方法
// 开始接收好友列表
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
NSLog(@"开始接收好友列表");
}
// 接收完毕
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
NSLog(@"结束接收好友列表");
}
// 每次接收到一个好友就会走一次这个方法
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{
NSString *jid = [[item attributeForName:@"jid"] stringValue];
XMPPJID *xmppjid = [XMPPJID jidWithString:jid resource:kResource];
[self.rosters addObject:xmppjid];
NSIndexPath *indexpath = nil;
if (self.rosters.count == 0) return;
indexpath = [NSIndexPath indexPathForRow:self.rosters.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexpath] withRowAnimation:(UITableViewRowAnimationLeft)];
}
// 收到添加好友请求 :(同意:[roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];)
//(拒绝:[roster rejectPresenceSubscriptionRequestFrom:presence.from];)
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence{
XMPPRoster *roster = [XMPPManager sharedManager].roster;
NSString *message = [NSString stringWithFormat:@"%@请求加你为好友", presence.from.user];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"好友请求" message:message preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"同意" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
// 同意请求
[roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];
}];
UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"拒绝" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
// 拒绝请求
[roster rejectPresenceSubscriptionRequestFrom:presence.from];
}];
[alert addAction:action1];
[alert addAction:action2];
[self presentViewController:alert animated:YES completion:nil];
}
6.添加好友(添加好友界面)
- (IBAction)actionAdd:(UIButton *)sender {
// 拿到花名册管理类
XMPPRoster *roster = [XMPPManager sharedManager].roster;
// 拼装要添加的好友
XMPPJID *userjid = [XMPPJID jidWithUser:self.txtUserName.text domain:kDomin resource:kResource];
// 添加好友请求
[roster subscribePresenceToUser:userjid];
}
7.建立聊天
7.1 声明属性 (声明一个XMPPJID类型的属性,记录要聊天的对象)
// 当前聊天对象
@property(nonatomic, strong) XMPPJID * jidChatTo;
7.2 选择聊天对象(可视化编程中)
点击好友列表页面中的一个好友,将这个好友的XMPPJID传给聊天页面。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([sender isKindOfClass:[UITableViewCell class]]) {
NSIndexPath *index = [self.tableView indexPathForCell:(UITableViewCell *)sender];
XMPPJID *jid = self.rosters[index.row];
// 属性传值
ChatViewController *chatVC = [segue destinationViewController]; // 获取目标控制器
chatVC.jidChatTo = jid;
}
7.3 声明聊天界面属性
声明聊天相关的控件属性以及其他属性
@property (weak, nonatomic) IBOutlet UITableView *tableChat; // 展示聊天记录
@property (weak, nonatomic) IBOutlet UITextField *txtMessage; // 输入消息内容
@property (weak, nonatomic) IBOutlet UIView *viewInput; // 底部的输入控件
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *constraintBottomFromSuper; // 底部frame约束
@property (nonatomic, strong) NSMutableArray *messages; // 存放聊天信息
7.4 获取聊天记录
单例(XMPPManager)里面声明CoreData的上下文对象属性,以及聊天消息的归档处理类的对象属性
/**
* 消息归档处理类
* 程序关闭后,下次打开还可以再次查看以前的聊天记录
*/
@property(nonatomic, strong) XMPPMessageArchiving * messageArchiving;
/**
* coredata 上下文,用来获取通过messageArchiving归档后存储起来的消息
*/
@property(nonatomic, strong) NSManagedObjectContext * context;
单例对象初始化(init方法)里面对上下文,和归档处理对象的一些操作
//------------------初始化 XMPPMessageArchiving------
// xmppMessageArchiving的主要功能:1、通过通讯管道获取到服务器发送过来的消息。2、将消息存储到指定的XMPPMessageArchivingCoreStorage
// xmpp为我们提供的一个存储聊天消息的coredata仓库
XMPPMessageArchivingCoreDataStorage *xmacds = [XMPPMessageArchivingCoreDataStorage sharedInstance];
// 初始化时,需要给这个归档类指定一个存储仓库
self.messageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:xmacds dispatchQueue:dispatch_get_main_queue()];
// 在通讯管道中激活
[self.messageArchiving activate:self.stream];
// 获取消息归档类提供的上下文信息
self.context = xmacds.mainThreadManagedObjectContext;
聊天页面里面通过单例中获得的上下文对象,从CoreData中取到聊天记录数据
// 加载所有信息(通过单例类的上下文获取)
- (void)reloadAllMessage{
// 获取上下文信息
NSManagedObjectContext *context = [XMPPManager sharedManager].context;
// xmppMessageArchving : 把接收到得消息归档,归档后的数据类型是:XMPPMessageArchiving_Message_CoreDataObject
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];
// 设置断言
// 查找所有的和当前聊天对象的聊天记录
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr = %@",self.jidChatTo.bare];
// 让断言生效
[fetchRequest setPredicate:predicate];
// 获取数据
NSArray *array = [context executeFetchRequest:fetchRequest error:nil];
if (array) {
// 将原来的数据清空
[self.messages removeAllObjects];
}
// 把获取的数据添加到当前数据源中
[self.messages addObjectsFromArray:array];
// 刷新列表
[self.tableChat reloadData];
// 将视图定位到最新的一条消息。
if (array.count > 0) {
NSIndexPath *index = [NSIndexPath indexPathForItem:array.count - 1 inSection:0];
[self.tableChat scrollToRowAtIndexPath:index atScrollPosition:(UITableViewScrollPositionBottom) animated:YES];
}
}
展示聊天内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
// 获取对应的消息
XMPPMessageArchiving_Message_CoreDataObject *message = self.messages[indexPath.row];
// 判断是不是自己发出去的
if ([message isOutgoing]) {
cell.textLabel.text = [NSString stringWithFormat:@"我:%@",message.body];
}else{
cell.textLabel.text = [NSString stringWithFormat:@"%@:%@",message.bareJidStr, message.body];
}
return cell;
}
7.5 发送和接收消息
设置通讯管道代理
XMPPStream *stream = [XMPPManager sharedManager].stream;
[stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
通讯管道代理中和发送以及接收消息相关的代理方法
// 收到信息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
NSLog(@"接收到一条消息:%@",message);
[self reloadAllMessage]; // 加载一遍所有的聊天数据
}
// 发送信息
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
NSLog(@"消息【%@】发送成功",message);
[self reloadAllMessage]; // 加载一遍所有聊天数据
}
发送消息
- (IBAction)actionSendMsg:(UIButton *)sender {
XMPPStream *stream = [XMPPManager sharedManager].stream;
// 实例化一个消息类
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.jidChatTo];
// 设置消息内容
[message addBody:self.txtMessage.text];
// 通过通讯管道发送
[stream sendElement:message];
}
其他:处理键盘弹出相关:
注册键盘frame改变的通知:
// 通过通知中心来观察键盘的frame的变化,当键盘frame发生变化后触发keyboardFrameChange事件
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChange:) name:UIKeyboardDidChangeFrameNotification object:nil];
处理方法及键盘回收代理事件:
// 键盘frame改变后触发事件
- (void)keyboardFrameChange:(NSNotification *)sender{
// 键盘改变后的frame
CGRect rect = [[sender.userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
// 计算出聊天窗口的底部偏移量
CGFloat height = self.view.frame.size.height - rect.origin.y;
self.constraintBottomFromSuper.constant = height;
}
#pragma mark -UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}