美文网首页iOS开发专区IM即时通讯
XMPPFramework开发(六):聊天功能和消息回执

XMPPFramework开发(六):聊天功能和消息回执

作者: 神经骚栋 | 来源:发表于2016-12-23 13:30 被阅读3702次

    前言


    前面我们几篇文章我们一直在说关于XMPPFramework中好友关系相关的东东,今天即时通讯最重要的是什么?通讯聊天呀,所以,我们今天就说一下XMPPFramewor聊天的实现.SDChat比较简陋的聊天界面如下所示.


    XMPPFramework实现聊天功能的相关方法


    在XMPPFramework中实现聊天功能的类主要是XMPPSteamXMPPMessageArchivingXMPPMessageArchivingCoreDataStorage,其中XMPPSteam的作用主要是消息发送、消息接受等一系列相关的代理回调.XMPPMessageArchivingXMPPMessageArchivingCoreDataStorage则是消息历史记录本地查询的相关类.下面则是关于聊天的相关方法.

    //发送消息
    - (void)sendElement:(NSXMLElement *)element;
    
    //消息发送成功的代理回调
    -(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message;
    
    //消息发送失败的代理回调
    -(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error;
    
    //接受到新的消息
    - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message;
    

    XMPPFramework的聊天功能实现


    聊天功能的逻辑相对好友方面的逻辑说还是比较简单的.我们先看一下两个用户之间发送一条消息的流程图.如下所示.


    通过上面的流程图我们已经对XMPPFramework的发送消息的整体流程有了一个大概的了解.那么接下来我们就说一下SDChat中关于聊天功能的代码实现.

    首先,在SDXmppManager中我们先对聊天功能的本地化存储对象进行初始化.

    @property(nonatomic,strong)XMPPMessageArchiving *messageArchiving;
    @property(nonatomic,strong)NSManagedObjectContext *messageContext;
    
    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;
    

    上面.我们已经对本地化存储对象进行了设置,接下来,我们现在SDChatVC聊天界面中做一个准备工作,那就是获取聊天历史记录.我们需要通过SDXmppManager中的messageContext(上下文属性对象)来获取到对应的数据.

    NSManagedObjectContext *context = [SDXmppManager defaulManager].messageContext;
    

    然后我们需要调用CoreData中的NSFetchRequest(获取数据的请求,通过被管理数据的上下文来执行查询)这个类的相关方法查询对应的数据.这里需要使用到上述的上下文对象context.数据库中的数据很多,我们需要的只是与当前聊天对象的记录,所以我们还需要使用谓词来添加过滤条件.这样我们就可以获取到我们想要的数据了.如下代码所示,fetchedObjects就是我们所需要的数组,数组中的对象类型为MPPMessageArchiving_ Message_CoreDataObject.

    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 == %@", [SDXmppManager defaulManager].stream.myJID.bare,self.chatToPeople.jid.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(@"和此人的激情交谈");
        
    }
    
    

    上面关于CoreData数据库的查询看似很是麻烦,但是系统已经帮我们写好了代码块了,我们只需要输入"fetch"就可以找到如下的列表,我们选择第一个"fetch - Core Data Fetch"就把上面所的所有的代码输入了.如图所示.

    那么,准备工作做好之后,我们就需要开始发送消息了.发送消息也是十分的简单,我们使用- (void)sendElement:(NSXMLElement *)element这方法就可以发送我们的消息了,但是在此之前,我们需要组装我们所需要发送的XMPPMessage消息对象.我们设置类型为"chat"类型,然后设置需要接受者的JID,接受者的信息需要从联系人列表页面中传值过来.然后我们添加XMPPMessage的Body信息为我们的文本信息.代码如下.

    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToPeople.jid];
    [message addBody:self.inputView.inputTextView.text];
    

    那么接下来,我们就发送的我们的消息即可.

    [[SDXmppManager defaulManager].stream sendElement:message];
    

    发送如果成功(对方接收到消息)之后,我们就需要在-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message发送消息成功这个代理方法中做数据刷新的操作代码如下所示.

    //消息发送成功
    -(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{   
        [self reloadMessage];
    }
    

    作为消息的接受者也是要聊天记录的刷新的,这个需要在-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message这个方法中进行代码实现,具体如下所示.

    //消息接收成功
    -(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
        [self reloadMessage]; 
    }
    
    

    消息回执


    上面的模块,我们好像把消息的一个流程做完了,但是其实不然,如果只有上面的几步,我们是实现了基本的即时通讯,但是会出现信息不同步的Bug,那就是我们同时发送两条消息的时候,我们的页面上只会出现上一条消息,最后发送的消息是不出现的,这是什么原因造成的呢?用户发送的消息,其实是没有回执.就是说A发送消息给B.而B又一致不回复.所以对于客户端A来讲压根不知道消息是否发送成功.所以我们需要在每一条消息的后面添加回执消息,这个需要在组装消息之后进行添加.如下所示.

    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToPeople.jid];
    [message addBody:self.inputView.inputTextView.text];
    NSXMLElement *receipt = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
    [message addChild:receipt];
    
    

    那么当接受者接受到消息之后,我们需要往发送者发送回执消息,通知发送者消息已经接受成功了,代码如下所示.

    NSXMLElement *request = [message elementForName:@"request"];
    if (request)
    {
        if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
        {
            //组装消息回执
            XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"] to:message.from elementID:[message attributeStringValueForName:@"id"]];
            NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
            [msg addChild:recieved];
            
            //发送回执
            [[SDXmppManager defaulManager].stream  sendElement:msg];
        }
    }else
    {
        NSXMLElement *received = [message elementForName:@"received"];
        if (received)
        {
            if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
            {
                //发送成功
                NSLog(@"message send success!");
            }
        }
    }
    

    有了消息回执之后,但是如果接受者不在当前的聊天页面却是在线的,我们发送一条消息何如发送回执消息呢?这就需要我们在AppDelegate里面设置一个接收消息的代理方法用来发送回执消息,代码和上面的一样.这里就做截图了,不进行重复操作了.

    聊天记录的展现


    上面我们基本把聊天功能实现完成了,如何实现聊天记录的展现呢?页面的整体是一个tableView,根据文字消息的内容,我们计算出每一个气泡的高度,然后计算出每一个Cell的高度.那么逻辑上是这样的,实际上在SDChat中是如何实现的?这里我就一个一个功能点来说明.

    对于每一个聊天记录的Cell,我们把文字Label添加到气泡的UIImageView对象上去.然后气泡的UIImageView对象再添加到每一个Cell上.Cell主要有两个功能点,一个就是文字的高度,另外一个就是气泡的拉伸问题.

    首先,我们说一下计算文字高度的方法.我们使用的是NSString中的一个方法,我们输入文字的可展示范围以及文字的字号就可以.可以计算出一段文字的Rect.

    - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullable NSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingContext *)context;
    

    那么计算出文字高度之后,我们如何让我们的气泡适应文字的尺寸呢?我们需要对图片进行拉伸操作,我们使用的是如下的方法.其中参数1代表从左侧到指定像素禁止拉伸,该像素之后拉伸,参数2代表从上面到指定像素禁止拉伸,该像素以下就拉伸.

    - (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;
    

    在聊天页面的tableView的其实就没有什么好说的,最多就是一个去除Cell之间的间隔线问题,我们只需要下面一行代码即可.

    self.chatTableView.separatorStyle = NO;
    

    聊天历史记录获取Bug以及优化建议


    其实这个Bug是关于中文的问题,在设置个人的电子名片和聊天记录的时候都会出现这个问题,那么就是如果我们输入了中文,那么下一次获取数据的时候可能只有"???"了,问题待解决,如果有解决的大神,希望能告诉骚栋,红包答谢~😄

    结束


    呼呼,写到这一篇,骚栋XMPPFramework的这个专题也进行了一大半了,下一篇骚栋将写一下关于好友上下线的状态的获取.希望大家能继续关注,如果有任何问题欢迎联系骚栋,谢谢.最后还把SDChat的传送门送给大家.大家可以对照着Demo来看本篇博客.

    -->SDChat传送门🚪

    相关文章

      网友评论

      • Alan_yo:你这么写的话,后台需要加入什么插件么?
        神经骚栋:@Alan_yo 这不是正常逻辑吗?
        Alan_yo:@神经骚栋 我在接收回調也加了这段回执,不过 app 杀死之后还会再收到之前的消息.是服务器那边问题还是需要再加什么?
        神经骚栋:@Alan_yo open fire 我在第一篇中写过
      • 黑黑的小土豆:这 代码格式。。。
        神经骚栋:@OBJECT_C NSXMLElement *request = [message elementForName:@"request"];
        if (request)
        {
        if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
        {
        //组装消息回执
        XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"] to:message.from elementID:[message attributeStringValueForName:@"id"]];
        NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
        [msg addChild:recieved];

        //发送回执
        [[SDXmppManager defaulManager].stream sendElement:msg];
        }
        }else
        {
        NSXMLElement *received = [message elementForName:@"received"];
        if (received)
        {
        if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
        {
        //发送成功
        NSLog(@"message send success!");
        }
        }
        }
        OBJECT_C:你的DEMO有个问题,就是最后一条消息看不到,NSXMLElement *receipt = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
        [message addChild:receipt]; 这个加了,但是还是看不到
        神经骚栋:@黑黑的小土豆 见笑见笑
      • Gradlyarn::skull:
        神经骚栋:@dormitory219 :skull:
      • HoyaWhite:0->1好爽的感觉
        神经骚栋:@WhitePlimsolls 哦哦
        HoyaWhite:@神经骚栋 还没呢,U3d那么多东西,我只是基于AR做一些需求 现在接收了一个外包项目有XMPP的东西在里面
        神经骚栋:@WhitePlimsolls 啥,你要研究这个了?U3d呢?搞完了?

      本文标题:XMPPFramework开发(六):聊天功能和消息回执

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