
搞事前言
前一篇博客,我們對(duì)XMPPFramework的登錄注冊(cè)功能以及邏輯做了詳細(xì)的說(shuō)明,用戶(hù)登錄完成之后,我們需要做的就是獲取到當(dāng)前賬號(hào)的好友列表和個(gè)人信息,今天這一篇博客就是對(duì)好友列表的相關(guān)邏輯以及代理方法來(lái)做一下講解說(shuō)明.我們先看看SDChat中的好友列表示意圖.
XMPPFramework中好友關(guān)系說(shuō)明解釋
在XMPPFramework中呢,好友關(guān)系是可以通過(guò)訂閱來(lái)實(shí)現(xiàn)的,也就是說(shuō)A與B相互訂閱,那么A與B就是好友了,如果A只是訂閱了B,B沒(méi)有訂閱A,那么我們就說(shuō)A與B兩者不是好友,當(dāng)然了,我在實(shí)際過(guò)程中搞好友添加的邏輯還是比較多的,這里需要了解A與B相互訂閱(openfire服務(wù)器中訂閱狀態(tài)為both,當(dāng)然了,訂閱狀態(tài)也有from和to,這樣的也算是好友.具體情況后面會(huì)詳細(xì)說(shuō)明),那么A與B就是好友這一個(gè)邏輯即可.

好友列表獲取流程.
當(dāng)用戶(hù)登錄成功之后,我們做的最主要的一個(gè)模塊就是加載好友列表模塊.那么好友加載模塊的整體流程是怎樣的呢?我們先看一個(gè)SDChat好友列表的流程圖,幫助我們熟悉好友列表在實(shí)際過(guò)程中如何展現(xiàn)的.(圖片可能看不清楚,請(qǐng)自行下載查看,謝謝.)

好友服務(wù)器數(shù)據(jù)獲取代碼部分
XMPPFramework中好友列表的管理核心類(lèi)是XMPPRoster,這個(gè)類(lèi)可以用來(lái)對(duì)好友的信息獲取,添加,刪除等操作.在SDChat中,我們把XMPPRoster聲明為SDXmppManager的一個(gè)屬性對(duì)象,并且在初始化過(guò)程中激活好友模塊.代碼如下所示.(說(shuō)明:XMPPRosterCoreDataStorage對(duì)象使用存儲(chǔ)好友數(shù)據(jù)的.)
self.rosterCoreDataStorage= [XMPPRosterCoreDataStorage sharedInstance];
self.roster = [[XMPPRoster alloc]initWithRosterStorage:self.rosterCoreDataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
//激活roster
[self.roster activate:self.stream];
其實(shí)好友獲取是有兩種方式的,騷棟使用的是代理方法獲取好友節(jié)點(diǎn)的.由于代理方法默認(rèn)的是在登錄成功之后默認(rèn)就會(huì)調(diào)用獲取好友節(jié)點(diǎn),但是我做頁(yè)面的時(shí)候需要調(diào)節(jié)一下好友節(jié)點(diǎn)獲取的時(shí)機(jī),所以,我就把XMPPRoster的自動(dòng)獲取好友節(jié)點(diǎn)功能關(guān)掉了.當(dāng)然了,你可以使用自動(dòng)獲取.這個(gè)需要根據(jù)實(shí)際情況而定,實(shí)現(xiàn)代碼如下所示.
self.roster.autoFetchRoster = NO;
這樣在我們登錄完成之后,我們需要在- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender這個(gè)方法中手動(dòng)調(diào)起獲取好友的方法.調(diào)起方法也很簡(jiǎn)單,只需要一行代碼就可以.
[[SDXmppManager defaulManager].roster fetchRoster];
當(dāng)我們調(diào)起了獲取好友的方法之后,我們需要在在聯(lián)系人列表(SDContactsVC)這個(gè)控制器中先設(shè)置XMPPRoster對(duì)象的代理.我是在初始化就設(shè)置了代理對(duì)象.
[[SDXmppManager defaulManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
設(shè)置完成之后代理方法其實(shí)總共是有三個(gè)的,三個(gè)代理調(diào)取的實(shí)際分別是所有好友節(jié)點(diǎn)獲取開(kāi)始,每一個(gè)好友節(jié)點(diǎn)獲取到的時(shí)候,所有好友節(jié)點(diǎn)獲取完成之后,我們可以根據(jù)實(shí)際情況來(lái)進(jìn)行不同的操作.比如我們?cè)讷@取開(kāi)始之前初始化好友節(jié)點(diǎn)數(shù)組,獲取結(jié)束刷新頁(yè)面等等,具體的三個(gè)代理方法如下所示.
//開(kāi)始獲取好友節(jié)點(diǎn)列表的時(shí)候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender;
//獲取每一個(gè)好友節(jié)點(diǎn)的時(shí)候
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;
//結(jié)束獲取好友節(jié)點(diǎn)列表的時(shí)候
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;
這是我只使用了后面的兩個(gè)代理方法,這是有原因的,因?yàn)槲业暮糜压?jié)點(diǎn)數(shù)組([SDUser defaulUser].contactsArray)不需要每一次獲取都進(jìn)行更新,而且這三個(gè)代理方法在實(shí)際使用過(guò)程中,自動(dòng)調(diào)取的次數(shù)很多,比如添加完好友或者刪除完好友都能自動(dòng)調(diào)取這個(gè)三個(gè)代理方法,為了不必要的麻煩,所以我只是用了后面的兩個(gè)代理方法.還是那句話,大家可以根據(jù)自己的實(shí)際情況自行調(diào)用不同的代理方法.
我們先看一下SDChat中在-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;騷棟都做了什么樣的操作.首先,我們獲取到每一個(gè)好友節(jié)點(diǎn)item,然后我們先判斷item節(jié)點(diǎn)的訂閱信息subscription的值,我們需要的好友是雙方相互訂閱,也就是"subscription”屬性為"both"、"from”、”to”才是我們需要的好友節(jié)點(diǎn).所以符合這三種情況的都是我們需要的好友節(jié)點(diǎn),所以if的篩選條件就出來(lái)了,如下代碼所示.其他訂閱類(lèi)型不同的節(jié)點(diǎn)我們后面會(huì)說(shuō)到具體的情況.
if ([[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"both"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"from"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"to"]) {
}
在篩選完成之后,我們需要做的事情就是獲取到item節(jié)點(diǎn)的JID信息了,這里我們只需要兩行代碼就可以完成了.
NSString *SJid = [[item attributeForName:@"jid"] stringValue];
XMPPJID *jid = [XMPPJID jidWithString:SJid];
不管是前期界面上顯示好友的JID信息,還是后期顯示電子名片信息,我們都需要先遍歷好友節(jié)點(diǎn)數(shù)組([SDUser defaulUser].contactsArray)判斷數(shù)組中是否已經(jīng)存在該好友信息了.這樣做的原因是因?yàn)?code>-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;這個(gè)代理方法可能對(duì)一個(gè)好友節(jié)點(diǎn)獲取多次,如果我們不進(jìn)行選擇性的添加的話,數(shù)組可能會(huì)出現(xiàn)好友重復(fù)的現(xiàn)象的,所以我們需要先判斷是否存在該好友信息,具體代碼如下所示.(關(guān)于isDeleteFriend布爾值的存在的意義,當(dāng)我們刪除好友的時(shí)候,訂閱信息subscription的值可能并不是"remove",有可能是"both",所有我們這里需要加一個(gè)布爾值,后面的刪除好友,我們會(huì)詳細(xì)說(shuō)明的.)
BOOL isExist = NO;
for (SDContactModel *contact in self.user.contactsArray) {
if ([contact.jid.user isEqualToString:jid.user]) {
isExist = YES;
}
}
判斷完是否存在好友信息之后,我們就可以根據(jù)isExist這個(gè)布爾值來(lái)判斷了是否要添加數(shù)據(jù)了,添加數(shù)據(jù)過(guò)程如下代碼所示,這里我是獲取了好友的名片信息進(jìn)行的添加,前期的話可以直接添加JID.
if (!isExist) {
//添加數(shù)據(jù)
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];
}
上面就是從服務(wù)器獲取到好友數(shù)據(jù)的基本流程了.
好友數(shù)據(jù)本地整理代碼部分
當(dāng)我們獲取好友數(shù)據(jù)完成之后,我們并不是直接展現(xiàn)到頁(yè)面上,我們需要對(duì)好友數(shù)據(jù)進(jìn)行整理然后再展現(xiàn)到界面之上.我們通過(guò)-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;這個(gè)代理方法來(lái)調(diào)取我們的好友數(shù)據(jù)整理方法-(void)networkingWithContactsArray;.
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
[self networkingWithContactsArray];
}
SDChat的好友界面是類(lèi)似于微信的好友界面的,是分組展示的.所以,數(shù)據(jù)存儲(chǔ)的整體思路是,列表的數(shù)據(jù)源是存儲(chǔ)于一個(gè)字典當(dāng)中,我們把首字母相同的JID或者是用戶(hù)名存儲(chǔ)于一個(gè)數(shù)組當(dāng)中.每個(gè)key是每一個(gè)JID的首字母大寫(xiě)(或者是用戶(hù)名稱(chēng)的首字母大寫(xiě)).因?yàn)樽值涫菬o(wú)序的,那么如何做到有序的排列呢?我們需要建立另外一個(gè)數(shù)組作為排序數(shù)組,同時(shí)也起著索引數(shù)組的作用.我們把字典中所有的Key放入數(shù)組中,然后排序,數(shù)據(jù)提取過(guò)程中我們只需要根據(jù)數(shù)組的排列順序拿取即可.示意圖如下所示.

那么我們看一下實(shí)際代碼過(guò)程中,對(duì)于字典的數(shù)據(jù)添加整理部分,首先我們要初始化字典對(duì)象,然后我們遍歷好友節(jié)點(diǎn)數(shù)組([SDUser defaulUser].contactsArray),取出我們需要排序的每一個(gè)關(guān)鍵字符串(不管是JID還是用戶(hù)名).我們調(diào)用-(NSString *)transform:(NSString *)chinese這個(gè)方法回去首字母并且大寫(xiě).在這個(gè)方法中我們有幾種情況需要處理,一種是獲取字符串失敗,也就是說(shuō)傳入的是一個(gè)nil值,我們直接返回 "#" ,另外一種是如果首字母是數(shù)字,那么我們也是需要返回"#"的.所以這樣返回首字母所使用到的方法總共就有了三個(gè),兩個(gè)用來(lái)判斷是否是數(shù)組,一個(gè)則是截取并且進(jìn)行字母大寫(xiě)的操作.三個(gè)方法如下所示.
//截取首字母并且大寫(xiě)
-(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];
}
//判斷是否為浮點(diǎn)型:
- (BOOL)isPureFloat:(NSString*)string{
NSScanner* scan = [NSScanner scannerWithString:string];
float val;
return[scan scanFloat:&val] && [scan isAtEnd];
}
那么通過(guò)返回首字母,我們需要判斷一下當(dāng)前的字典對(duì)象中是否已經(jīng)存在了改分組的數(shù)組,如果存在,那么直接存儲(chǔ),如果不存在,那么初始化一個(gè)數(shù)組之后,以首字母為Key,空數(shù)組為Value保存到字典中,然后再把數(shù)據(jù)存儲(chǔ)到數(shù)組中去.具體代碼如下所示.
if (self.contactsPinyinDic[firstWord] ==nil) {
//如果聯(lián)系人字典數(shù)組中沒(méi)有該分組,那么就初始化一個(gè)分組數(shù)組,然后存儲(chǔ).
NSMutableArray *sectionArray =[NSMutableArray arrayWithCapacity:16];
[sectionArray addObject:contact];
[self.contactsPinyinDic setValue:sectionArray forKey:firstWord];
}else{
NSMutableArray *sectionArray =self.contactsPinyinDic[firstWord];
[sectionArray addObject:contact];
}
添加完成之后,我們就需要對(duì)索引數(shù)組進(jìn)行操作了,首先我們還是先初始化我們的索引數(shù)組,初始化的過(guò)程中,我們就把所有的key值添加到我們的數(shù)組當(dāng)中去.然后我們需要添加一個(gè)空字符串@"",這是為了給"新的朋友"那個(gè)分組做準(zhǔn)備的.代碼如下所示.
self.indexArray = [NSMutableArray arrayWithArray:self.contactsPinyinDic.allKeys];
[self.indexArray addObject:@""];//添加一個(gè)空的字符串,用于菜單分組
然后,我們對(duì)索引數(shù)組進(jìn)行遍歷排序操作.
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;
}
}
}
如果存在"#",為了界面的美觀,我們把"#"放在索引數(shù)組的最后一位,然后,我們就刷新我們的頁(yè)面即可.
//索引數(shù)組移動(dòng)#號(hào)到最后.
if ([self isIncludeWithJing]) {
[self.indexArray removeObject:@"#"];
[self.indexArray addObject:@"#"];
}
[self.contactsList reloadData];
這樣在tableView的數(shù)據(jù)源方法中,分組個(gè)數(shù)為索引數(shù)組的元素個(gè)數(shù)self.indexArray.count;每一個(gè)section中元素的個(gè)數(shù)(除了一個(gè)分組)都是為字典中對(duì)應(yīng)的每一個(gè)value數(shù)組的個(gè)數(shù).
然后,我們就可以做出開(kāi)始的界面的樣子來(lái)了.當(dāng)然了,這樣的畫(huà)面需要我們做很多工作的,也是我們下一篇博客所要說(shuō)到的,電子名片的實(shí)現(xiàn).
結(jié)束
SDChat中的好友獲取的邏輯和代理方法就說(shuō)到這里了,這里我要先聲明一下,SDChat中可能還存在著B(niǎo)ug,如果有任何問(wèn)題,歡迎聯(lián)系騷棟,謝謝.接下來(lái)的一篇我覺(jué)得應(yīng)該先把XMPPFramework電子名片的實(shí)現(xiàn)說(shuō)一下,XMPPFramework我覺(jué)得最坑的就是添加好友這一塊了,邏輯比較多,準(zhǔn)備在第五篇中進(jìn)行講解說(shuō)明.希望大家持續(xù)關(guān)注~最后把SDChat的傳送門(mén)送給大家.大家可以對(duì)照著Demo來(lái)看本篇博客.
-->SDChat傳送門(mén)??
