华山论剑之浅谈XMPP协议实现即时通讯功能

优秀的代码是它自己最好的文档。当你考虑要添加一个注释时,问问自己,“如何能改进这段代码,以让它不需要注释?”*


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文件中设置服务器相关信息!!!


如果有有什么疑问,可以回复,谢谢大家
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容