即时通信3
xmpp整理笔记:用户网络连接及好友的管理
xmpp中的用户连接模块包括用户的上线与下线信息展现,用户登录,用户的注册; 好友模块包括好友的添加,好友的删除,好友列表的展示。
在xmpp中 负责数据传输的类是xmppStream,开发的过程中,针对不同的传输内容,会调用不同的代理方法,在使用XMPPFramework开发时,只需要在不同的代理方法中,填写相同的代码即可。
往期回顾:
xmpp整理笔记:xmppFramework框架的导入和介绍 http://www.cnblogs.com/zhonghuaxiaodangjia/p/4323475.html
xmpp整理笔记:环境的快速配置(附安装包) http://www.cnblogs.com/zhonghuaxiaodangjia/p/4323460.html
一。大概的连接过程如下
1.运行后需要和服务器建立一个长连接,系统会反馈链接是否成功
2.成功时需要告诉服务器的用户的密码,服务器判断是否给予授权
3.成功授权后,告诉服务器上线了。
4.将要离开时告诉服务器,我需要断开链接了。
5.服务器反馈你可以断开了,然后你再告诉服务器你下线了
二。首先,需要知道 XMPPStreamDelegate 和 XMPPRosterDelegate 的一些代理方法
如果你不是在董铂然博客园看到本文 请点击查看原文
xmpp流代理方法:
连接成功时调用
- (void)xmppStreamDidConnect:(XMPPStream *)sender
断开连接时调用
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
授权成功时调用
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
授权失败时调用
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
注册成功时调用
- (void)xmppStreamDidRegister:(XMPPStream *)sender
注册失败时调用
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error
xmppRoster花名册代理方法
接收到好友请求时调用
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
三。用户的登录:
用户需要在连接成功后的代理方法中 将自己的密码发送给服务器,自己的密码应该是在点击登录的时候就和其他信息一起存入偏好设置了,在现在需要的时候可以轻而易举的取出来。在发送验证请求的时候会用到这个方法authenticateWithPassword: 后面的error在实际开发中建议必须处理,我在这就偷懒了如下所示
1
2
3
4
5
6
7
8
9
10
|
/** 连接成功时调用 */ - ( void )xmppStreamDidConnect:(XMPPStream *)sender { NSLog (@ "连接成功" ); NSString *password = [[ NSUserDefaults standardUserDefaults] valueForKey:SXLoginPasswordKey]; // 将用户密码发送给服务器,进行用户登录 [ self .xmppStream authenticateWithPassword:password error: NULL ]; } |
然后等待结果,在授权成功后来到授权成功代理方法在这应该先告诉服务器用户上线,然后给发出成功通知,自己的AppDelegate在远处接收,一旦接收到通知马上更换应用程序的根控制器到进入后的界面,这里要注意这些代理方法都是在异步的,所以这里要用到线程间通讯,在主线程发送通知
1
2
3
4
5
6
|
// 通知服务器用户上线 [ self goOnline]; // 在主线程利用通知发送广播 dispatch_async(dispatch_get_main_queue(), ^{ [[ NSNotificationCenter defaultCenter] postNotificationName:SXLoginResultNotification object:@( YES )]; }); |
如果授权失败的话,应该断开与服务器的链接,并且把开始存储的用户偏好清空(因为这些是错误的没用),然后再到主线程更新UI弹出一个框显示密码错误,并且发出失败通知,让APPDelegate切换根控制器到登录界面 (董铂然原创)
1
2
3
4
5
6
7
8
9
10
11
12
|
// 断开与服务器的连接 [ self disconnect]; // 清理用户偏好 [ self clearUserDefaults]; // 在主线程更新UI if ( self .failed) { dispatch_async(dispatch_get_main_queue(), ^ { self .failed(@ "用户名或者密码错误!" );}); } // 在主线程利用通知发送广播 dispatch_async(dispatch_get_main_queue(), ^{ [[ NSNotificationCenter defaultCenter] postNotificationName:SXLoginResultNotification object:@( NO )]; }); |
四。用户的上线和下线:
关于用户的上线和下线,需要用到一个类XMPPPresence 类。这个类是XMPPElement的子类,主要用来管理某些信息的展现。首先要实例化一个对象,这其中会用到一个presenceWithType 方法,有两个选择@"unavailable"代表下线,@"available"代表上线,一般情况上线的时候后面就可以直接省略。实例化之后用xmpp流发出去。如下所示
1
2
3
4
5
6
7
8
9
10
11
12
|
#pragma mark - ******************** 用户的上线和下线 - ( void )goOnline { XMPPPresence *p = [XMPPPresence presence]; [ self .xmppStream sendElement:p]; } - ( void )goOffline { XMPPPresence *p = [XMPPPresence presenceWithType:@ "unavailable" ]; [ self .xmppStream sendElement:p]; } |
对用户是否在线状态的判断
1
2
|
// 取出用户 XMPPUserCoreDataStorageObject *user = [ self .fetchedResultsController objectAtIndexPath:indexPath]; |
用户的 user.section 就是用户的状态
// section // 0 在线 // 1 离开 // 2 离线
五。用户注册:
自己在UI里搭建好注册页面,里面需要用户填写好用户信息。在点击注册按钮时,把单例类里自己设定的一个布尔值isRegisterUser 设置为YES。 然后重新发送连接请求。最终还是会来到,连接成功时的代理方法,刚才在这里发送用户密码登录的,现在可以加一层判断,如果isRegisterUser的值为YES 就不是发送用户密码登录了,而是发送用户密码注册,这里将会用到一个方法registerWithPassword:
1
2
3
4
5
6
|
if ( self .isRegisterUser) { // 将用户密码发送给服务器,进行用户注册 [ self .xmppStream registerWithPassword:password error: NULL ]; // 将注册标记复位 self .isRegisterUser = NO ; } |
然后有两个代理方法,注册成功和注册失败,分别写上合适的操作。
六。添加好友:
搭建一个加好友的UI只需要一个文本框和一个按钮。
在文本框的回车按钮点击代理方法中,做文本框是否为空得判断,不为空就添加好友,(添加好友方法可以抽出来写使得结构更加清晰)
添加好友方法如下:有两个注意点一个是判断用户是否写了域名,如果只是单单写了个账号,也可以自动帮他拼接个域名然后注册。还有个就是判断是否已经是自己的好友,如果是就不做任何操作。如果不是好友 那就马上添加。最后让导航控制器返回到登陆界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 添加好友 - ( void )addFriendWithName:( NSString *)name { // 你写了域名那更好,你没写系统就自动帮你补上 NSRange range = [name rangeOfString:@ "@" ]; // 如果没找到 NSNotFound,不要写0 if (range.location == NSNotFound ) { name = [name stringByAppendingFormat:@ "@%@" , [SXXMPPTools sharedXMPPTools].xmppStream.myJID.domain]; } // 如果已经是好友就不需要再次添加 XMPPJID *jid = [XMPPJID jidWithString:name]; BOOL contains = [[SXXMPPTools sharedXMPPTools].xmppRosterCoreDataStorage userExistsWithJID:jid xmppStream:[SXXMPPTools sharedXMPPTools].xmppStream]; if (contains) { [[[UIAlertView alloc] initWithTitle:@ "提示" message:@ "已经是好友,无需添加" delegate: nil cancelButtonTitle:@ "OK" otherButtonTitles: nil , nil ] show]; return ; } [[SXXMPPTools sharedXMPPTools].xmppRoster subscribePresenceToUser:jid]; [ self .navigationController popViewControllerAnimated: YES ]; } |
这里会用到一个通过JID加好友的方法subscribePresenceToUser: 但是这个方法是通过Roster 调用的所以要在单例类里导入头文件 声明属性,遵守协议,实现代理方法(董铂然原创)
在单例类里所有特殊类的操作都要写在xmppStream的懒加载里
1
2
3
4
5
6
7
8
|
// 实例化 _xmppReconnect = [[XMPPReconnect alloc]init]; _xmppRosterCoreDataStorage = [XMPPRosterCoreDataStorage sharedInstance]; _xmppRoster = [[XMPPRoster alloc]initWithRosterStorage:_xmppRosterCoreDataStorage dispatchQueue:dispatch_get_global_queue(0, 0)]; // 激活 [_xmppRoster activate:_xmppStream]; // 添加代理 [_xmppRoster addDelegate: self delegateQueue:dispatch_get_main_queue()]; |
接受到加好友请求的代理方法
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
在这个方法中,先要拼接提示的字符串,就是从 presence.from(申请人的id)的人请求加你为好友。然后设置弹窗,确定和拒绝,点击确定按钮后
1
2
|
// 接受好友请求 [ self .xmppRoster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster: YES ]; |
这个弹窗建议使用iOS8的新功能 UIAlertController。 这样可以不用写alertDelegate 也能设置确定按钮点击事件 。用 alert addAction: 添加按钮,把点击事件写在block里,最后再取到当前窗口的根控制器弹出presentViewController,相当于以前的show 。iOS8苹果的思想渐渐是想把所有弹出控制器的各种方法都慢慢统一到present。
补充:这个功能就是QQ上所谓的加好友不需要验证,是布尔值可以控制开关。
1
2
|
// 取消接收自动订阅功能,需要确认才能够添加好友! _xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = NO ; |
七。好友列表的展示。
这里需要用到查询结果调度器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
- ( NSFetchedResultsController *)fetchedResultsController { if (_fetchedResultsController != nil ) { return _fetchedResultsController; } // 指定查询的实体 NSFetchRequest *request = [[ NSFetchRequest alloc]initWithEntityName:@ "XMPPUserCoreDataStorageObject" ]; // 在线状态排序 NSSortDescriptor *sort1 = [ NSSortDescriptor sortDescriptorWithKey:@ "sectionNum" ascending: YES ]; // 显示的名称排序 NSSortDescriptor *sort2 = [ NSSortDescriptor sortDescriptorWithKey:@ "displayName" ascending: YES ]; // 添加排序 request.sortDescriptors = @[sort1,sort2]; // 添加谓词过滤器 request.predicate = [ NSPredicate predicateWithFormat:@ "!(subscription CONTAINS 'none')" ]; // 添加上下文 NSManagedObjectContext *ctx = [SXXMPPTools sharedXMPPTools].xmppRosterCoreDataStorage.mainThreadManagedObjectContext; // 实例化结果控制器 _fetchedResultsController = [[ NSFetchedResultsController alloc]initWithFetchRequest:request managedObjectContext:ctx sectionNameKeyPath: nil cacheName: nil ]; // 设置他的代理 _fetchedResultsController.delegate = self ; return _fetchedResultsController; } |
写完了结果调度器之后要切记在viewdidload页面首次加载中加上一句,否则不干活
1
2
|
// 查询数据 [ self .fetchedResultsController performFetch: NULL ]; |
结果调度器有一个代理方法,一旦上下文改变触发,也就是刚加了好友,或删除好友时会触发
1
2
3
4
5
|
- ( void )controllerDidChangeContent:( NSFetchedResultsController *)controller { NSLog (@ "上下文改变" ); [ self .tableView reloadData]; } |
整个tableview的数据源方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- ( NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:( NSInteger )section { return self .fetchedResultsController.fetchedObjects.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:( NSIndexPath *)indexPath { static NSString *ID = @ "ContactCell" ; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; XMPPUserCoreDataStorageObject *user = [ self .fetchedResultsController objectAtIndexPath:indexPath]; // 显示此好友是否相互关注 NSString *str = [user.jidStr stringByAppendingFormat:@ " | %@" ,user.subscription]; cell.textLabel.text = str ; // 这里有个自定义方法传入section 通过switch判断返回汉字。section关系到是否在线 cell.detailTextLabel.text = [ self userStatusWithSection:user.section]; return cell; } |
其中subscription是用户的好友互加情况
// 如果是none表示对方还没有确认 // to 我关注对方 // from 对方关注我 // both 互粉
再提一下 user.section 就是用户的状态
// section // 0 在线 // 1 离开 // 2 离线
当有好友上线,上下文改变时,结果调度器会重新排序,然后在线的好友会显示在上面。
八。删除好友
好友的列表显示界面可以给tableView添加滑动删除。(开启编辑模式)
1
2
3
4
5
6
7
8
|
#pragma mark - ******************** 开启编辑模式删除好友 - ( void )tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:( NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { XMPPUserCoreDataStorageObject *user = [ self .fetchedResultsController objectAtIndexPath:indexPath]; XMPPJID *jid = user.jid; // 接下来是设置弹窗 |
在弹窗的点击事件里面删除好友用到的方法是
1
|
[[SXXMPPTools sharedXMPPTools].xmppRoster removeUser:jid]; |