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


前言


前面我们几篇文章我们一直在说关于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传送门🚪


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

推荐阅读更多精彩内容

  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,347评论 0 15
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 一直想写点东西,可是一直都没真正坚持下来。从最开始的创业手记,到后来的博客,再到最近的微信公众帐号,无一例外的“三...
    isncteam阅读 385评论 2 3
  • 三亚湾的随拍
    Goly_LAN阅读 280评论 4 5