我们一般将XMPP的封装成一个工具类来解决注册,登录
1.首先得初始化单例:消息通道:XMPPStream,好友列表:XMPPRoster,消息到达管理器:XMPPMessageArchiving,消息上下文:NSManagedObjectContext
- (instancetype)init
{
self = [super init];
if (self) {
//1.信息管道
self.xmppStream = [[XMPPStream alloc] init];
self.xmppStream.hostName = kHostName; // 这个宏是xmpp框架里提供的. 这里引入一个端口的概念.
self.xmppStream.hostPort = kHostPort; // openfire的端口号默认是5222;
// 让self成为xmppStream的一个代理.
// 小重点!:dispatch_get_main_queue,一般情况下代理都是子线程中完成某个动作, 这样设置之后, 触发xmppStream的代理动作之后,所有的操作会在主线程中进行.
[self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()]; // 写完这个,下面写一个方法, 向服务器发送连接请求的方法.
//2.初始化好友列表
XMPPRosterCoreDataStorage * rosterCoreDataStorage = [XMPPRosterCoreDataStorage sharedInstance]; // 这个是好友存放,持久化方案,xmpp框架为我们准备好了一个coredata来帮我们持久化.
self.xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:rosterCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
[self.xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
[self.xmppRoster activate:self.xmppStream]; // activate激活. 这个roster就可以从这个stream通信管道中获取好友信息.
//3.消息到达观察
XMPPMessageArchivingCoreDataStorage * messageArchivingCoreDataStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
[self.xmppMessageArchiving activate:self.xmppStream]; // 激活收发信息的观察.
//4.消息管理的上下文
self.messageManagedObjectContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
}
return self;
}
2.注册:(1)先连接服务(2)成功后再注册.不连接上服务器是无法注册的,而且连接的时候必须先断开链接,再链接,如果当前是连接状态的话尝试再次连接是会崩溃
(1)先连接服务
-(void)connectToServerWithUser:(NSString *)user
{
//1)先断开链接再链接
if ([self.xmppStream isConnected]) {
[self.xmppStream disconnect]; // 如果想连接新的的通信管道,我们默认这个请求连接是为了刷新或者请求新的连接.
}
// 2)设置链接双方:IP和账号
// 发送连接之前, 你需要告诉服务器你是谁, 你要向谁发送连接?.用户名:域名:应用源名
XMPPJID * jid = [XMPPJID jidWithUser:user domain:kDomin resource:kResource];
self.xmppStream.myJID = jid;
// 3)真正的连接动作
NSError * error = nil;
[self.xmppStream connectWithTimeout:3.0f error:&error]; // 连接结果通过代理返回. 小重点
if (error != nil) {
NSLog(@"连接出错:%@",error);
}
}
XMPPStreamDelegate
//手机端和服务器端将要形成长连接,但动作还没开始
- (void)xmppStreamWillConnect:(XMPPStream *)sender;
{
}
(2)注册:连接服务器时服务器已经知道了我们的账号,此时只需注册密码就行然后根据错误判断是否链接成功
// 手机端和服务器端已经连接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender
{
if ([self.connectType isEqualToString:@"Login"]) {
NSError * loginError;
[self.xmppStream authenticateWithPassword:self.loginPassword error:&loginError];
// 这个是登录的动作, 那么登录成功与否的动作,应该由谁来执行呢?
if (loginError) {
NSLog(@"验证连接失败:%@",loginError);
}
}else if([self.connectType isEqualToString:@"Regist"])
{
NSError * registError;
[self.xmppStream registerWithPassword:self.registerPassword error:®istError];
if (registError) {
NSLog(@"注册连接失败:%@",registError);
}
}
}
// 与服务器连接超时3秒,即是失败的
-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender
{
NSLog(@"与服务器连接超时");
}
//注册成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender
//注册失败
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error
//登录成功的代理方法
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
//设置在线状态
XMPPPresence * presence = [XMPPPresence presenceWithType:@"available"];
[[XMPPManager defaultManage].xmppStream sendElement:presence];
}
//登录失败的代理方法
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
3.登录:
(1)登录类似注册,登录的具体动作用[self.xmppStream authenticateWithPassword:self.loginPassword error:&loginError]而已
(2)登录与注册另外不同的是登录成功后要设置在线状态:
presence的状态:
available上线
away离开
do not disturb忙碌
unavailable下线
XMPPPresence*presence = [XMPPPresencepresenceWithType:@"available"];[xmppStreamsendElement:presence];
因为我们在单例初始化时已经做过获取好友列表的操作了所以在这只需遵循代理从代理方法里面获取就行了(
(2)添加代理
[[XMPPManager defaultManage].xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
(3)实现方法
#pragma mark - XMPPRosterDelegate
// 开始获取好友列表
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender
{
NSLog(@"开始获取好友列表");
}
// 正在获取好友列表 ,获取一个就走一次这个方法.
- // none:互相没有订阅
- // to:你订阅了别人,他同意了,但是他并没有订阅你
- // from:你被他订阅了,但你没有订阅他
- // both:互粉
- // remove:删除好友
item:
添加对方时:
<item jid="18333333333@casing.com" ask="subscribe" subscription="none"></item>
<item jid="18333333333@casing.com" subscription="to"></item>
<item jid="18333333333@casing.com" subscription="both"></item>
同意添加对方好友:
<item jid="18333333333@casing.com" subscription="from"></item>
<item jid="18333333333@casing.com" subscription="both"></item>
好友状态:
<item jid="18333333333@casing.com" subscription="both"></item>
删除
<item jid="18333333333@casing.com" ask="unsubscribe" subscription="from"></item>
<item jid="18333333333@casing.com" ask="unsubscribe" subscription="none"></item>
<item jid="18333333333@casing.com" subscription="remove"></item>
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item
{
NSLog(@"%@",item);
// 从一个xml结构中获取jid
NSString * jidStr = [[item attributeForName:@"jid"] stringValue];
XMPPJID * jid = [XMPPJID jidWithString:jidStr]; //238792708@qq.com,可以拿XMPPJID.user属性作为TableView的列表区分
[self.allRoster addObject:jid]; //数组存储XMPPJID
NSIndexPath * index = [NSIndexPath indexPathForRow:self.allRoster.count-1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[index]withRowAnimation:UITableViewRowAnimationBottom];
}
// 已经完成获取好友列表
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender
{
NSLog(@"获取好友列表完成.");
}
5.添加好友
XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",name,kDomin]];
[[XMPPManager defaultManage].xmppRoster subscribePresenceToUser:jid];
6.删除好友
// 从服务器删除好友
NSString *userName = [NSString stringWithFormat:@"%@@casing.com", friendName];
[[XMPPHelper shareXmppHelper].xmppRoster
removeUser:[XMPPJID jidWithString:userName]];
7.同意添加好友,拒绝添加好友
下边这个方法可以直接写在单例里边比较方便
XMPPRosterDelegate:
//收到添加好友的请求
//presence:<presence xmlns="jabber:client" type="subscribe" to="18337125565@casing.com" from="18333333333@casing.com"><status>未命名</status></presence>
-(void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
{
self.fromJID = presence.from;
UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"请求添加您为好友" delegate:self cancelButtonTitle:@"叔叔我不约" otherButtonTitles:@"接受", nil];
[alertView show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (buttonIndex) {
case 0:{
// 我拒绝成为你的好友.
[self.xmppRoster rejectPresenceSubscriptionRequestFrom:self.fromJID];
break;
}
case 1:{
// 我答应成为你的好友.
[self.xmppRoster acceptPresenceSubscriptionRequestFrom:self.fromJID andAddToRoster:YES];
break;
}
default:
break;
}
}
8.修改联系人备注
NSString *userName = [NSString stringWithFormat:@"%@@casing.com", self.contactModel.account];
[[XMPPHelper shareXmppHelper].xmppRoster setNickname:textF.text forUser:[XMPPJID jidWithString:userName]];
9.发送消息
:发送消息用sendElement,发送的消息是XMPPMessage,包含了XMPPJID,发送过成功之后的消息会自动保存到XMPPMessageArchiving_Message_CoreDataObjec这个表中,可随时查询,发送成功之后调用reloadMessage,作用是查出自己和对方的账号的聊天记录并刷新TableVIew.
- (IBAction)sendMessage:(id)sender {t
// 给谁发送信息呢?
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJID];
[message addBody:@"强哥你好"];
// 将消息发送出去
[[XMPPManager defaultManage].xmppStream sendElement:message];
[self reloadMessage];
// 成功或者失败,走代理方法. 这个方法是xmppStream来返回的, 所以我们需要成为xmppStream的代理.
}
XMPPStreamDelegate:
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
NSLog(@"信息发送成功");
[self reloadMessage];
}
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error
{
NSLog(@"信息发送失败:%@",error);
}
-(void)reloadMessage
{
// ? 聊天信息对应的是哪个模型呢呢?
NSManagedObjectContext * managedObjectContext = [XMPPManager defaultManage].messageManagedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
// 谓词匹配,检索条件.否者会检索出所有的聊天信息.所以这里需要过滤一下.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr = %@ AND streamBareJidStr = %@", self.chatToJID.bare,[XMPPManager defaultManage].xmppStream.myJID.bare];
[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 = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// 没有检索到任何内容
}else
{
if (fetchedObjects.count == 0) {
return;
}
// 采取我们的操作.
[self.allMessages removeAllObjects];
[self.allMessages addObjectsFromArray:fetchedObjects];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.allMessages.count -1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
10.接收消息
用下边的方法可以接收消息并且可以拿到消息的内容,然后调用reloadMessage,当对方发送的消息XMPPMessage到达后此方法就会执行一次,然后自动存到CoreDate的表XMPPMessageArchiving_Message_CoreDataObject中,然后我们调用方法reloadMessage取出来并按时间排序,展示到TableView中即可
XMPPStreamDelegate:
// 接受消息的方法
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
NSLog(@"接收到了一条信息");
NSLog(@"%@",message.body);
[self reloadMessage];
}