美文网首页即时通讯技术iOS技术交流收藏iOS进阶指南
华山论剑之浅谈XMPP协议实现即时通讯功能

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

作者: 神经骚栋 | 来源:发表于2016-03-04 22:48 被阅读518次

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

    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文件中设置服务器相关信息!!!
    如果有有什么疑问,可以回复,谢谢大家

    相关文章

      网友评论

        本文标题:华山论剑之浅谈XMPP协议实现即时通讯功能

        本文链接:https://www.haomeiwen.com/subject/iunnkttx.html