优秀的代码是它自己最好的文档。当你考虑要添加一个注释时,问问自己,“如何能改进这段代码,以让它不需要注释?”*
XMPP简介
XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能。
XMPP协议的实现原理过程
当我们知道XMPP是一种协议的时候,我们如何通过objective-c 代码实现XMPP协议,进而实现我们的即时通讯功能呢? 下面的图片就是为我们做了很好的解释.
XMPP协议的代码实现
1.准备工作
我们做的客户端也服务器通讯通道的实现以及数据交流,那么首先要有我们自己的服务器,当然了,在公司的好说一些,如果是个人研究技术怎么办呢?我们可以自己搭建一个服务器或者使用leancloud这种第三方服务器,这里我给大家提供一些搭建服务器的工具,当然了,自己搭建的服务器生命比较脆弱,请大家好好爱护~还有就是leancloud也是我推荐的一种方法.
----->点击前往LeanCloud官方网站
----->XMPP本地服务器搭建工具下载
2.OC搭建Client和连接通道
工程完成目标:
1.创建通讯通道并完成账号密码的登录
2.创建通道并完成账号的申请
3.好友列表的获取和显示
4.即时通讯功能的实现
首先,我们需要导入我们的所需要导入的XMPPFramework(PS:点击打开下载,完成之后直接解压拖到工程中😃),还有手动的导入两个库.如下.
libxml2.tbd
libresolv.tbd
然后,我们就要配置我们的build setting页面的设置 search paths 选添加一个字段,添加如下
/usr/include/libxml2
完成上面的设置之后 我们需要创建一个单例类XMPPManager,用它来创建通讯通道实现上面的四个功能.
XMPPManager.h中如下
#import <Foundation/Foundation.h>
#import "XMPPFramework.h"
@interface XMPPManager : NSObject
//通讯管道
@property(nonatomic,strong)XMPPStream *stream;
//和通讯录对象很像,用来管理好友类~
@property(nonatomic,strong)XMPPRoster *roster;
//XMPP聊天消息本地化处理对象
@property(nonatomic,strong)XMPPMessageArchiving *messageArchiving;
//消息上下文对象
@property(nonatomic,strong)NSManagedObjectContext *messageContext;
+(instancetype)defaulManager;
-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password;
-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password;
//与服务器断开链接
-(void)disconnectWithServer;
@end
XMPPManager.h文件的解释:
stream : 这是C和S之间的通讯通道.
roster : 这个属性管理好友列表的一个属性.
messageArchiving : 这个属性用来管理本地聊天记录的一个类
messageContext : 消息上下文对象.
+(instancetype)defaulManager:创建单例的方法
-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password : 登录的方法
-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password : 注册新账号的方法
-(void)disconnectWithServer; 与服务器断开链接
了解完各个方法之后,我们就要在XMPPManager.m实现一下,实现如下
#import "XMPPManager.h"
//代表与服务器进行连接的类型.
typedef enum : NSUInteger {
DoLgin,
DORegiser,
} ConnetType;
@interface XMPPManager()<XMPPStreamDelegate,XMPPRosterDelegate,XMPPMessageArchivingStorage>
@property(nonatomic,strong)NSString *password;
@property(nonatomic,strong)NSString *regiserPassword;
//声明一个属性 记录连接的类型
@property(nonatomic,assign)ConnetType type;
@end
@implementation XMPPManager
static XMPPManager *manager;
+(instancetype)defaulManager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManager alloc]init];
});
return manager;
}
-(instancetype)init{
if (self = [super init]) {
self.stream = [[XMPPStream alloc]init];
self.stream.hostName = kHostName;
self.stream.hostPort = kHostPort;
//设置stream的代理
[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//下面这一堆其实是对roster对象进行初始化.
//系统写好的XMPP存储对象
XMPPRosterCoreDataStorage *dataStorage = [XMPPRosterCoreDataStorage sharedInstance];
self.roster = [[XMPPRoster alloc]initWithRosterStorage:dataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
//激活roster
[self.roster activate:self.stream];
//给roster对象指定代理
[self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
//初始化聊天记录管理对象
XMPPMessageArchivingCoreDataStorage *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
//激活管理对象
[self.messageArchiving activate:self.stream];
//设置管理对象代理
[self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
}
return self;
}
//与服务器的建立链接
-(void)connectToServerWintUser:(NSString *)name{
if ([self.stream isConnected]) {
[self.stream disconnect];
}
//jid jabberID,是基于jabber协议的由用户名生成的唯一ID
self.stream.myJID = [XMPPJID jidWithUser:name domain:kDomin resource:kResource];
NSError *error = nil;
//与服务器建立链接.
[self.stream connectWithTimeout:30.0f error:&error];
if (error != nil) {
@throw [NSException exceptionWithName:@"CQ_Error" reason:@"与服务器建立连接失败,请查看代码" userInfo:nil];
}
}
//与服务器断开链接
-(void)disconnectWithServer{
[self.stream disconnect];
}
-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password{
self.password = password;
self.type = DoLgin;
[self connectToServerWintUser:name];
}
//与服务器建立连接
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
NSLog(@"与服务器建立链接正常");
//与服务器进行登录认证
NSError *error = nil;
switch (self.type) {
case DoLgin:
[self.stream authenticateWithPassword:self.password error:&error];
if (error != nil) {
NSLog(@"认证过程出错!");
}
break;
case DORegiser:
[self.stream registerWithPassword:self.regiserPassword error:&error];
if (error != nil) {
NSLog(@"注册过程出错!");
}
break;
default:
break;
}
}
-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
@throw [NSException exceptionWithName:@"CQ_Error" reason:@"与服务器建立连接超时,请查看代码" userInfo:nil];
}
-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password{
self.type = DORegiser;
self.regiserPassword = password;
[self connectToServerWintUser:name];
}
看完了上面的代码,连我自己都觉得乱乱的,所以 我们一个功能一个功能看这些代码的实现原理,
登录功能
我们想要登录我们的服务器,首先要有我们的账号和密码,然后我们就需要建立通道
(a) defaulManager
创建单例这个方法中就是创建了我们的单例.
(b) init
初始化这个方法中我们需要对我们的通讯管道属性stream进行初始化一下,设置stream服务器IP地址和服务器端口,还有就是设置stream的代理对象.实现XMPPStreamDelegate协议方法.这里设置代理对象的方法不同于以前,这里是使用runtime设计模式可以为stream设置多个代理对象.
(c) LoginWithUserName:(NSString )name AndPassWord:(NSString )password
这个方法中首先我们需要保存我们的密码,用于传值到下一个方法中.self.type = DoLgin;这句代码有作何解释呢?因为不管是登录和注册,我们都要与我们的服务器创建联系,那么服务器是如何知道我们是创建的什么联系的呢?就是通过这句代码实现的,当然了,现在你可能听得糊涂,当看到下面的方法的时候你就明白了.[self connectToServerWintUser:name];这句代码就是要创建于服务器之间的联系.这时候,账号name就通过参数的形式传到了connectToServerWintUser这个函数,而password通过属性的传值到xmppStreamDidConnect(当完成通道的建立的时候执行的代理方法).
(d) connectToServerWintUser
[self.stream disconnect];这句代码就是让客户端断开连接通道,综合上面来看,当我们已经存在的连接的通道的时候,我们就会让通道断开,这是为什么呢?因为C与S之间的通道只能创建一条,当我们重复创建的时候,就会导致我们的程序崩溃.所以我们要保障我们的通道是只有一条的. [self.stream connectWithTimeout:30.0f error:&error];这句代码就是我们创建通道,当然了等待时间是30秒.
(e)xmppStreamDidConnect
这是一个代理的方法,当我们完成通道的创建之后,我们就会调用这个方法,在这个代理方法中我们需要做的就是对我们的密码进行验证.[self.stream authenticateWithPassword:self.password error:&error];这就是登录验证我们的密码.验证成功之后就会调用-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender这个代理方法,验证失败就会调用-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error 这个代理方法,当然了,这两个方法是写在我们的登录页面的,因为我们需要对使用者有个用户的交互不是?比如弹出一个弹窗.提醒一下用户.xmppStreamDidConnect不管是注册和登录都会调用,我们怎么区分呢?我们现在.m文件的顶部设置了一个枚举值,通过枚举值的值判断我们所需要的操作.
(f)xmppStreamConnectDidTimeout
这个方法就是说,当我们与服务器建立连接超时的时候会进行的操作.@throw [NSException exceptionWithName:@"CQ_Error" reason:@"与服务器建立连接超时,请查看代码" userInfo:nil];是我们手动的抛出一个异常.
注册功能
注册功能与登录功能在实现上是相似的,下面的属性就是区别的开始.
@property(nonatomic,strong)NSString *password;
@property(nonatomic,strong)NSString *regiserPassword;
xmppStreamDidConnect
在这个方法中,我们需要对我们的注册方法与登录方法分别开来. [self.stream registerWithPassword:self.regiserPassword error:&error];这个方法就是我们把注册的密码传到服务器上保存的方法.当然了,当我们注册成功的时候,就会调动-(void)xmppStreamDidRegister:(XMPPStream *)sender这个协议方法,当注册不成功的时候,我们就会调用-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error这个方法.这里我们也要拍给我们的用户一些交互,让我们用户知道自己注册的结果.
好友列表功能
在XMPPManager.m文件中,我们需要做的就是在 *** init *** 方法中对管理好友列表的roster对象进行一下初始化并且制定代理,这里需要注意一个地方,当我们设置roster的初始化的时候,我们需要使用 *** dispatch_get_global_queue(0, 0) ***全局线程,不能使用主线程,原因是如果使用主线程会出现一些莫名其妙的Bug. [self.roster activate:self.stream]; 激活roster的意思就是给roster可以通过stream通道的权限..(PS:大白话 😂)
在我们的好友列表页面中,首先我们需要确定他是一个UITableViewController,然后我们需要从我们的服务器拿到我们的好友的列表数组.通道stream连接在我们的登录的时候已经完成了,所以我们不需要再管理通道了,我们需要在好友列表的控制器中实现XMPPRosterDelegate的代理方法来获取到我们的好友列表.代码如下
在MainTableViewController.m中
#import "MainTableViewController.h"
#import "XMPPManager.h"
#import "ChatTableViewController.h"
@interface MainTableViewController ()<XMPPRosterDelegate>
//用来存储所有的好友信息的.
@property(nonatomic,strong)NSMutableArray *dataArray;
@end
@implementation MainTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.dataArray = [NSMutableArray array];
[[XMPPManager defaulManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
//roster代理方法
//开始获取好友列表的时候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
NSLog(@"开始获取好友列表");
}
//结束获取好友列表
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
NSLog(@"获取好友列表完成的时候.");
}
//获取好友信息的时候
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{
// //将每一个好友存储下来.
// NSLog(@"%@",[item children]);
//
// NSLog(@"%@",[item name]);
NSString *SJid = [[item attributeForName:@"jid"] stringValue];
//把字符串类型的JID转换成XMPPJID
XMPPJID *jid = [XMPPJID jidWithString:SJid];
//把JID存储到数组中,相当修改数据源
[self.dataArray addObject:jid];
//更新UI
NSIndexPath *path = [NSIndexPath indexPathForRow:self.dataArray.count-1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MainCell" forIndexPath:indexPath];
//把字符串类型的JID转换成XMPPJID
XMPPJID *jid = self.dataArray[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"%@ %@ %@",jid.user,jid.resource,jid.domain];
return cell;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UITableViewCell *cell = (UITableViewCell *)sender;
//拿到下一步要跳转的controller对象
ChatTableViewController *chatVC = segue.destinationViewController;
//判断选中的cell在当前的Table中的位置
NSIndexPath *path = [self.tableView indexPathForCell:cell];
chatVC.chatToJID = self.dataArray[path.row];
}
@end
方法解释
在上面的代码中我们要解释的只有两个方法 ,一个是获取好友信息的时候调用的
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item
另外一个是我们点击好友进入聊天页面所需要的方法.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item
这个是XMPPRosterDelegate协议中代理方法,如果我们有很多的好友,这个方法会调用很多次,知道我们的好友全部遍历完. NSString *SJid = [[item attributeForName:@"jid"] stringValue]; 和XMPPJID *jid = [XMPPJID jidWithString:SJid];这两个方法就是当我们从服务器接到我们的数据时候,我们要先将他转化成一下,转成成我们所需要的数据,然后存入我们的数据源数组中.更新UI 使用的方法是[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom]; 为什么使用这个方法呢?为什么不使用reloadData这个方法?因为这个代理方法会执行很多次,我们为了避免没有必要的内存负担,所以我们只需要更新一下我们最后一条数据就行,这样大大减少了内存的负担,提高了我们的工程效率.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
这个方法是因为我使用storyboard的原因,我们为了能正确找到我们点击对应的cell所使用的方法.传值的时候我们需要把JID传到聊天界面,这样服务器就会清楚的知道我们是对谁进行聊天了.
聊天界面功能
对于聊天界面的搭建,我们也是使用到UITableViewController,逻辑是我们需要往服务器发送我们的消息,然后服务器在通过JID发送到指定的消息,发送者发送消息的时候和接收者接收到消息的时候刷新我们的UI.
那我们就先看看在XMPPManager的类中我们需要做一些什么事情吧.
在XMPPManager.h中 我们创建了两个属性,一个是XMPP聊天消息本地化处理对象的messageArchiving,另外一个是消息上下文对象messageContext.
在XMPPManager.m中 的init方法中,我们对这两个属性进行了初始化.如下
//初始化聊天记录管理对象
XMPPMessageArchivingCoreDataStorage *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
//激活管理对象
[self.messageArchiving activate:self.stream];
//设置管理对象代理
[self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
messageArchiving对象也是需要我们激活通道权限的.然后设置了代理.
在ChatTableViewController.h界面 我们需要设置一个XMPPJID对象 来接受好友列表传来的JID值.代码如下.
#import <UIKit/UIKit.h>
#import "XMPPManager.h"
@interface ChatTableViewController : UITableViewController
//接收好友列表传来的JID
@property(nonatomic,strong)XMPPJID *chatToJID;
@end
在ChatTableViewController.m文件中,我们需要做的就是实现XMPPStreamDelegate的代理方法,从代理方法中实现往服务器发送数据和从服务器接收数据的操作.代码如下
#import "ChatTableViewController.h"
@interface ChatTableViewController ()<XMPPStreamDelegate>
//存放所有的消息
@property(nonatomic,strong)NSMutableArray *messageArray;
@end
@implementation ChatTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.messageArray = [NSMutableArray array];
//添加代理
[[XMPPManager defaulManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//更新聊天记录信息
[self reloadMessage];
// 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;
}
//展现聊天记录
-(void)reloadMessage{
NSManagedObjectContext *context = [XMPPManager defaulManager].messageContext;
#pragma mark----直接一个 fet 下面全都出来了----
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//这里面要填的是XMPPARChiver的coreData实例类型
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
//对取到的数据进行过滤,传入过滤条件.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@", [XMPPManager defaulManager].stream.myJID.bare,self.chatToJID.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 = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
//完成之后干什么?
NSLog(@"和此人的激情交谈");
}
/********获取和这个人所有的聊天记录***************/
//清空聊天数组中的消息
[self.messageArray removeAllObjects];
//将新的聊天记录添加到数组中
self.messageArray = [NSMutableArray arrayWithArray:fetchedObjects];
NSLog(@"%ld",self.messageArray.count);
//刷新UI
[self.tableView reloadData];
//将tableview直接滑动到最底部
NSIndexPath * indexpath = [NSIndexPath indexPathForRow: self.messageArray.count-1 inSection:0];
if (indexpath.row > 0) {
[self.tableView selectRowAtIndexPath:indexpath animated:YES scrollPosition:UITableViewScrollPositionBottom];
}
}
- (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 {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ChatCell" forIndexPath:indexPath];
//取到我们对应的信息
XMPPMessageArchiving_Message_CoreDataObject *message = self.messageArray[indexPath.row];
if (message.isOutgoing == YES) {
cell.detailTextLabel.text = message.body;
cell.textLabel.text = @"";
}else {
cell.textLabel.text = message.body;
cell.detailTextLabel.text = @"";
}
return cell;
}
//发送消息
- (IBAction)sendAction:(id)sender {
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJID];
[message addBody:@"nice to meet you"];
//发送消息
[[XMPPManager defaulManager].stream sendElement:message];
}
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
// tipWithMessage(@"消息发送成功!");
[self reloadMessage];
}
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error{
tipWithMessage(@"消息发送失败");
}
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
[self reloadMessage];
}
@end
页面的逻辑:
当我们进入聊天界面的时候,我们会先调用reloadMessage这个方法,从服务器下载当前用户与这个JID的聊天记录,当我们点击发送消息的时候,我们就会调用sendAction这个方法,发送到服务器上去,当我们发送成功后调用didSendMessage这个协议方法,然后在这协议方法中调用reloadMessage这个方法重新从服务器下载新的聊天记录并且刷新UI,然后服务器上有当前用户新的消息的时候,就会调用didReceiveMessage这个代理方法,我们只需要在这个方法中再次调用reloadMessage方法就可.(PS:因为我用的是storyboard 所有有些地方会有所不同😃)
方法解释:
-(void)reloadMessage
这个方法是整个聊天界面的核心.我们在这里面做的就是从服务器下载我们的数据.当我们下载完数据的时候,就要把我们数据源数组中所有的元素清空,然后将所有从服务器中下载的元素添加到我们的数组中.然后刷新我们的UI,当我一刷新之后tableView就会重头开始了,所以我们设置让最后一个cell显示在屏幕上.还有就是下面的一段代码,这是我们先前写好的,我们需要敲出 *** fetch *** 就会给我们提示,是不是很简答?😂 这段代码就是从网上下载我们所需要的数据.当然了,我们需要使用谓词对这些数据进行过滤.过滤后的数据才是我们所需要的数据.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//这里面要填的是XMPPARChiver的coreData实例类型
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
//对取到的数据进行过滤,传入过滤条件.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@", [XMPPManager defaulManager].stream.myJID.bare,self.chatToJID.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 = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
//完成之后干什么?
NSLog(@"和此人的激情交谈");
}
XMPP协议实现即时通讯的过程大体就是这样了,当然了,我还有一些功能没有完善,比如添加好友的功能.这将在后期进行再次的完善.谢谢大家的查看..
--->XMPP的Demo资源下载
在Demo拿到手的时候,我们首先要对我们的服务器IP进行设置.在XMPPConfig.h文件中设置服务器相关信息!!!
网友评论