XMPPFramework开发(三):好友列表


搞事前言


前一篇博客,我们对XMPPFramework的登录注册功能以及逻辑做了详细的说明,用户登录完成之后,我们需要做的就是获取到当前账号的好友列表和个人信息,今天这一篇博客就是对好友列表的相关逻辑以及代理方法来做一下讲解说明.我们先看看SDChat中的好友列表示意图.


XMPPFramework中好友关系说明解释


在XMPPFramework中呢,好友关系是可以通过订阅来实现的,也就是说A与B相互订阅,那么A与B就是好友了,如果A只是订阅了B,B没有订阅A,那么我们就说A与B两者不是好友,当然了,我在实际过程中搞好友添加的逻辑还是比较多的,这里需要了解A与B相互订阅(openfire服务器中订阅状态为both,当然了,订阅状态也有from和to,这样的也算是好友.具体情况后面会详细说明),那么A与B就是好友这一个逻辑即可.


好友列表获取流程.


当用户登录成功之后,我们做的最主要的一个模块就是加载好友列表模块.那么好友加载模块的整体流程是怎样的呢?我们先看一个SDChat好友列表的流程图,帮助我们熟悉好友列表在实际过程中如何展现的.(图片可能看不清楚,请自行下载查看,谢谢.)


好友服务器数据获取代码部分

XMPPFramework中好友列表的管理核心类是XMPPRoster,这个类可以用来对好友的信息获取,添加,删除等操作.在SDChat中,我们把XMPPRoster声明为SDXmppManager的一个属性对象,并且在初始化过程中激活好友模块.代码如下所示.(说明:XMPPRosterCoreDataStorage对象使用存储好友数据的.)

self.rosterCoreDataStorage= [XMPPRosterCoreDataStorage sharedInstance];

self.roster = [[XMPPRoster alloc]initWithRosterStorage:self.rosterCoreDataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];

//激活roster
[self.roster activate:self.stream];

其实好友获取是有两种方式的,骚栋使用的是代理方法获取好友节点的.由于代理方法默认的是在登录成功之后默认就会调用获取好友节点,但是我做页面的时候需要调节一下好友节点获取的时机,所以,我就把XMPPRoster的自动获取好友节点功能关掉了.当然了,你可以使用自动获取.这个需要根据实际情况而定,实现代码如下所示.

self.roster.autoFetchRoster = NO;

这样在我们登录完成之后,我们需要在- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender这个方法中手动调起获取好友的方法.调起方法也很简单,只需要一行代码就可以.

[[SDXmppManager defaulManager].roster fetchRoster];

当我们调起了获取好友的方法之后,我们需要在在联系人列表(SDContactsVC)这个控制器中先设置XMPPRoster对象的代理.我是在初始化就设置了代理对象.

[[SDXmppManager defaulManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

设置完成之后代理方法其实总共是有三个的,三个代理调取的实际分别是所有好友节点获取开始,每一个好友节点获取到的时候,所有好友节点获取完成之后,我们可以根据实际情况来进行不同的操作.比如我们在获取开始之前初始化好友节点数组,获取结束刷新页面等等,具体的三个代理方法如下所示.

//开始获取好友节点列表的时候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender;

//获取每一个好友节点的时候
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;

//结束获取好友节点列表的时候
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;

这是我只使用了后面的两个代理方法,这是有原因的,因为我的好友节点数组([SDUser defaulUser].contactsArray)不需要每一次获取都进行更新,而且这三个代理方法在实际使用过程中,自动调取的次数很多,比如添加完好友或者删除完好友都能自动调取这个三个代理方法,为了不必要的麻烦,所以我只是用了后面的两个代理方法.还是那句话,大家可以根据自己的实际情况自行调用不同的代理方法.

我们先看一下SDChat中在-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;骚栋都做了什么样的操作.首先,我们获取到每一个好友节点item,然后我们先判断item节点的订阅信息subscription的值,我们需要的好友是双方相互订阅,也就是"subscription”属性为"both"、"from”、”to”才是我们需要的好友节点.所以符合这三种情况的都是我们需要的好友节点,所以if的筛选条件就出来了,如下代码所示.其他订阅类型不同的节点我们后面会说到具体的情况.

if ([[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"both"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"from"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"to"]) {

}

在筛选完成之后,我们需要做的事情就是获取到item节点的JID信息了,这里我们只需要两行代码就可以完成了.

NSString *SJid = [[item attributeForName:@"jid"] stringValue];
     
XMPPJID *jid = [XMPPJID jidWithString:SJid];

不管是前期界面上显示好友的JID信息,还是后期显示电子名片信息,我们都需要先遍历好友节点数组([SDUser defaulUser].contactsArray)判断数组中是否已经存在该好友信息了.这样做的原因是因为-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;这个代理方法可能对一个好友节点获取多次,如果我们不进行选择性的添加的话,数组可能会出现好友重复的现象的,所以我们需要先判断是否存在该好友信息,具体代码如下所示.(关于isDeleteFriend布尔值的存在的意义,当我们删除好友的时候,订阅信息subscription的值可能并不是"remove",有可能是"both",所有我们这里需要加一个布尔值,后面的删除好友,我们会详细说明的.)

BOOL isExist = NO;
            
for (SDContactModel *contact in self.user.contactsArray) {
                
       if ([contact.jid.user isEqualToString:jid.user]) {
                    
            isExist = YES;
                    
          }
}         

判断完是否存在好友信息之后,我们就可以根据isExist这个布尔值来判断了是否要添加数据了,添加数据过程如下代码所示,这里我是获取了好友的名片信息进行的添加,前期的话可以直接添加JID.

if (!isExist) {
    //添加数据
    XMPPvCardTemp *vCard =  [[SDXmppManager defaulManager].vCardTempModule vCardTempForJID:jid shouldFetch:YES];
    
    SDContactModel *contact =[[SDContactModel alloc]init];
    contact.jid = jid;
    contact.vCard =vCard;
    contact.isAvailable = NO;
    
    [self.user.contactsArray addObject:contact];
   
}

上面就是从服务器获取到好友数据的基本流程了.


好友数据本地整理代码部分

当我们获取好友数据完成之后,我们并不是直接展现到页面上,我们需要对好友数据进行整理然后再展现到界面之上.我们通过-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;这个代理方法来调取我们的好友数据整理方法-(void)networkingWithContactsArray;.

-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{

    [self networkingWithContactsArray];

}

SDChat的好友界面是类似于微信的好友界面的,是分组展示的.所以,数据存储的整体思路是,列表的数据源是存储于一个字典当中,我们把首字母相同的JID或者是用户名存储于一个数组当中.每个key是每一个JID的首字母大写(或者是用户名称的首字母大写).因为字典是无序的,那么如何做到有序的排列呢?我们需要建立另外一个数组作为排序数组,同时也起着索引数组的作用.我们把字典中所有的Key放入数组中,然后排序,数据提取过程中我们只需要根据数组的排列顺序拿取即可.示意图如下所示.

那么我们看一下实际代码过程中,对于字典的数据添加整理部分,首先我们要初始化字典对象,然后我们遍历好友节点数组([SDUser defaulUser].contactsArray),取出我们需要排序的每一个关键字符串(不管是JID还是用户名).我们调用-(NSString *)transform:(NSString *)chinese这个方法回去首字母并且大写.在这个方法中我们有几种情况需要处理,一种是获取字符串失败,也就是说传入的是一个nil值,我们直接返回 "#" ,另外一种是如果首字母是数字,那么我们也是需要返回"#"的.所以这样返回首字母所使用到的方法总共就有了三个,两个用来判断是否是数组,一个则是截取并且进行字母大写的操作.三个方法如下所示.

//截取首字母并且大写
-(NSString *)transform:(NSString *)chinese{
    
    if (chinese == nil ||[chinese isEqualToString:@""]) {
        
        return @"#";
        
    }
    
    NSMutableString *pinyin = [chinese mutableCopy];
    CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformMandarinLatin, NO);
    CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformStripCombiningMarks, NO);
    
    NSString *subString = [[pinyin uppercaseString] substringWithRange:NSMakeRange(0, 1)];
    
    if ([self isPureInt:subString] || [self isPureFloat:subString]) {
        
        return  @"#";
    }
    
    return subString;
}

//判断是否为整型:
- (BOOL)isPureInt:(NSString*)string{
    NSScanner* scan = [NSScanner scannerWithString:string];
    int val;
    return [scan scanInt:&val] && [scan isAtEnd];
}
//判断是否为浮点型:
- (BOOL)isPureFloat:(NSString*)string{
    NSScanner* scan = [NSScanner scannerWithString:string];
    float val;
    return[scan scanFloat:&val] && [scan isAtEnd];
}

那么通过返回首字母,我们需要判断一下当前的字典对象中是否已经存在了改分组的数组,如果存在,那么直接存储,如果不存在,那么初始化一个数组之后,以首字母为Key,空数组为Value保存到字典中,然后再把数据存储到数组中去.具体代码如下所示.

if (self.contactsPinyinDic[firstWord] ==nil) {
    
    //如果联系人字典数组中没有该分组,那么就初始化一个分组数组,然后存储.
    NSMutableArray *sectionArray =[NSMutableArray  arrayWithCapacity:16];
    
    [sectionArray addObject:contact];
    
    [self.contactsPinyinDic setValue:sectionArray forKey:firstWord];
    
}else{
    
    NSMutableArray *sectionArray =self.contactsPinyinDic[firstWord];
    
    [sectionArray addObject:contact];
    
}

添加完成之后,我们就需要对索引数组进行操作了,首先我们还是先初始化我们的索引数组,初始化的过程中,我们就把所有的key值添加到我们的数组当中去.然后我们需要添加一个空字符串@"",这是为了给"新的朋友"那个分组做准备的.代码如下所示.

self.indexArray = [NSMutableArray arrayWithArray:self.contactsPinyinDic.allKeys];
[self.indexArray addObject:@""];//添加一个空的字符串,用于菜单分组

然后,我们对索引数组进行遍历排序操作.

    for (int i = 0; i<self.indexArray.count; i++) {
        
        for (int j = 0; j<i; j++) {
            
            if (self.indexArray[j]>self.indexArray[i]) {
                
                NSString *objString = self.indexArray[j];
                self.indexArray[j] =  self.indexArray[i];
                self.indexArray[i] = objString;
                
            }
        }
    }

如果存在"#",为了界面的美观,我们把"#"放在索引数组的最后一位,然后,我们就刷新我们的页面即可.

//索引数组移动#号到最后.
if ([self isIncludeWithJing]) {
    
    [self.indexArray removeObject:@"#"];
    
    [self.indexArray addObject:@"#"];
}

[self.contactsList reloadData];

这样在tableView的数据源方法中,分组个数为索引数组的元素个数self.indexArray.count;每一个section中元素的个数(除了一个分组)都是为字典中对应的每一个value数组的个数.

然后,我们就可以做出开始的界面的样子来了.当然了,这样的画面需要我们做很多工作的,也是我们下一篇博客所要说到的,电子名片的实现.


结束


SDChat中的好友获取的逻辑和代理方法就说到这里了,这里我要先声明一下,SDChat中可能还存在着Bug,如果有任何问题,欢迎联系骚栋,谢谢.接下来的一篇我觉得应该先把XMPPFramework电子名片的实现说一下,XMPPFramework我觉得最坑的就是添加好友这一块了,逻辑比较多,准备在第五篇中进行讲解说明.希望大家持续关注~最后把SDChat的传送门送给大家.大家可以对照着Demo来看本篇博客.

-->SDChat传送门🚪


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

推荐阅读更多精彩内容

  • 前言 前面几篇文章我们主要搞了搞关于好友列表的相关技术以及逻辑,还有用户上下线监控这个没说,我准备放到最后再说,比...
    神经骚栋阅读 3,207评论 9 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 前言 上一篇博客中我们说到如何通过XMPPFramework中的代理方法来获取到好友节点数据信息,但是我们发现节点...
    神经骚栋阅读 3,458评论 6 10
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 我妈很笨,不会养花。这是我家公认的事实,除此之外我妈还不会养孩子,这是她自己讲的。但我否定。 一 我是个内向的人,...
    daladel阅读 454评论 0 0