XMPPFramework开发(三):好友列表

</br>

在软件开发中,无论是这种高级语言中总会陪伴着有些最为常用的设计格局,虽然就如iOS开发中与大家打交道最多的只是就是单例形式、观看者情势和工厂情势了,当然了此外的设置格局也如出一辙存在在编程的诸多地点。下面就就让大家简要的询问下观望者情势呢!

搞事前言


前一篇博客,大家对XMPPFramework的记名注册效用以及逻辑做了详实的印证,用户登录成功将来,我们需要做的就是得到到眼前账号的知心人列表和个人消息,前些天这一篇博客就是对忘年交列表的相干逻辑以及代理方法来做一下上书表明.大家先看看SDChat中的好友列表示意图.

</br>

观察者情势本质上时一种发表-订阅模型,用以消除具有不同行为的目的期间的耦合,通过这一形式,不同目的可以协同工作,同时它们也可以被复用于其余地点Observer从Subject订阅布告,ConcreteObserver落实重现ObServer并将其重载其update方法。一旦SubJect的实例需要公告Observer另外新的改变,Subject会发送update音讯来打招呼存储在其里面类中所注册的Observer、在ConcreteObserverupdate格局的莫过于贯彻中,Subject的中间景象可被获取并展开后续处理。其类图如下:

图片 1

观望者形式.png

XMPPFramework中好友关系表达解释


在XMPPFramework中吗,好友关系是可以经过订阅来实现的,也就是说A与B相互订阅,那么A与B就是好友了,要是A只是订阅了B,B没有订阅A,那么大家就说A与B两者不是忘年交,当然了,我在事实上过程中搞好友添加的逻辑仍然相比多的,这里需要了然A与B互相订阅(openfire服务器中订阅状态为both,当然了,订阅状态也有from和to,这样的也算是好友.具体情状后边会详细表达),那么A与B就是忘年交那么些逻辑即可.

</br>

由地点我们能够窥见观望者格局无非在是概念对象间的一种一对多的看重关系,并且当一个目的的情状暴发转移的时候,所有倚重于它的靶子都会获取关照且自动更新。即只要Subject同意任何观望者(实现了观察者接口的对象)对这多少个Subject的更动进行请阅,当Subject发送了变动,那么Subject会将以此转变发送给所有的观察者,观望者就能对Subject的更动做出更新。其时序图如下

图片 2

观望者情势2.png

好友列表获取流程.


当用户登录成功未来,我们做的最着重的一个模块就是加载好友列表模块.那么好友加载模块的完全流程是什么样的呢?大家先看一个SDChat好友列表的流程图,援救大家耳熟能详好友列表在实际过程中咋样显示的.(图片可能看不清楚,请自行下载查看,谢谢.)

</br>

知音服务器数据拿到代码部分

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];

}

下边就是从服务器获取到相知数据的着力流程了.

</br>

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

当我们取得好友数据形成将来,我们并不是从来表现到页面上,我们需要对好友数据举办整理然后再显示到界面之上.大家经过-(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数组的个数.

接下来,咱们就足以做出最先的界面的榜样来了.当然了,这样的画面需要大家做过多做事的,也是我们下一篇博客所要说到的,电子名片的实现.

</br>

经过地点的观测我们可以发现只要用N个Observer来拓展Subject的行为,这些Observer富有处理存储在Subject中的信息的一定实现,这样也就贯彻了后面所说的破除不同目的间的耦合的成效了。

结束


SDChat中的好友获取的逻辑和代理方法就说到这边了,这里自己要先声明一下,SDChat中恐怕还存在着Bug,假设有任何问题,欢迎联系骚栋,谢谢.接下来的一篇我以为应该先把XMPPFramework电子名片的贯彻说一下,XMPPFramework我觉着最坑的就是添加好友这一块了,逻辑相比多,准备在第五篇中展开教学表达.希望我们频频关注~最终把SDChat的传送门送给我们.我们可以相比着Demo来看本篇博客.

这就是说了然了这个我们或许就会更像明白下我们在咋样时候才会去接纳观察者模式吧?

  • 当需要将转移通告所有的靶猴时,而你又不清楚那一个目的的求实品种
  • 更改暴发在同一个对象中,并需要改变其他对象将有关的气象举办翻新且不精晓有些许个对象。

–>SDChat传送门🚪

</br>

而平等的在我们普通的开支中在Cocoa Touch框架中的的两种平常应酬的技巧KVO与通告都落实了寓目者形式,所以上边我们谈论的基本点也就是依据那五个地点的。

通知

在此前的博文中曾经简单的关系过一些通报的功底运用方法,所以部分主题的使用办法重新就不赘述。言归正传,在Cocoa Touch框架中NSNotificationCenterNSNotification对象实现了一对多的模子。通过NSNotificationCenter可以让对象之间开展报道,尽管那些目的期间并不认得。上面咱们来看下NSNotificationCenter宣布音信的措施:
   NSNotification  * subjectMessage = [ NSNotification  notificationWithName:@"subjectMessage"  object: self];
    NSNotificationCenter  * notificationCenter = [ NSNotificationCenter  defaultCenter];
    [notificationCenter postNotification:subjectMessage];

透过下面的代码大家创造了一个名为subjectMessageNSNotification对象,然后通过notificationCenter来发表这么些音信。通过向NSNotificationCenter类发送defaulCenter音信,可以拿走NSNotificationCenter实例的引用。每个过程中唯有一个默认的打招呼焦点,所以默认的NSNotificationCenter是个单例对象。假如有其余寓目者定于了其目的的连带事件则可以透过以下的情势来拓展操作:

    NSNotificationCenter  * notificationCenter1 = [ NSNotificationCenter  defaultCenter];
    [notificationCenter addObserver: self  selector: @selector(update:) name:@"subjectMessage"  object: nil ];

经过上述步骤大家早已向公告主题登记了一个风波同时经过selector制定了一个办法update:上边我们得以兑现以下这几个艺术

- (void)update:(NSNotification*)notification{

        if ([[notification name] isEqualToString:@"subjectMessage"]) {
            NSLog(@"%@",@"猴子派来的救兵去哪了?");

        }
}

理所当然最后只要我们需要对监听举行销毁

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

打探过通报将来大家来看一下KVO

KVO是Cocoa提供的一种名叫键值观看的编制,对象可以透过它拿到任何对象特定属性的更改通告。而这多少个机制是基于NSKeyValueObserving业余些,Cocoa因此这个协议为富有听从协议的对象提供了一种自动化的习性监听的意义。
虽然通知KVO都得以对观察者举行落实,不过她们之间如故略有不同的,由地点的事例我们可以见见布告是由一个主导目标为具有观望者提供变更公告,首如若广义上关心程序事件,而KVO则是被考察的靶子直接想观望者发送通知,首假若绑定于特定目的属性的值。下面我们经过一个简便的例证来询问下她的局部是应用方法

首先我们有Hero其一模型

@property (nonatomic,copy) NSString * name;
@property (nonatomic,copy) NSString * title;
@property (nonatomic,assign) NSUInteger age;

在决定其中我们将其开首化并赋值

    self.hero = [[Hero alloc] init];
    self.hero.name = @"赵云";
    self.hero.title = @"将军";
    self.hero.age = 87;

明天大家的这几个目的基本有值了,那么大家将以此目的的name监听下她的改动

[self.hero addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

接触通告并将值改变

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    self.hero.name = @"张飞";
}

在制定的回调函数中,处理收到的变更公告

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    if([keyPath isEqualToString:@"name"])
    {
        NSLog(@"赋值后--%@",self.hero.name);
        NSLog(@"新的值--%@",change[@"new"]);
        NSLog(@"以前的值--%@",change[@"old"]);

    }
}

回调打印如下:

图片 3

dayin.png

最后注销观察者

- (void)dealloc{
    [self.hero removeObserver:self forKeyPath:@"name"];
}

到了这边观看者情势中常用的KVO通知的情节就到此处,可是要明白这里谈及的只是最基础的用法,前面大家可能如故有越来越深切的追究,或者在继承中可能还会相比较iOS中的代理以及Block来寻找下iOS中的消息传递机制,再或者像Swift中的didSetwillSet的属性监听的不二法门,那些都是很有意思的始末,不是么?