美文网首页程序员
第五篇:XMPP实现IM--电子名片、用户在线状态监测

第五篇:XMPP实现IM--电子名片、用户在线状态监测

作者: 意一ineyee | 来源:发表于2018-07-11 15:15 被阅读127次

    目录

    一、电子名片
    1、注册时,为用户添加电子名片
    2、难点:需要显示用户的电子名片时,拉取电子名片
    3、修改个人信息时,更新电子名片
    二、用户上下线状态监测

    上一篇中,我们成功的拉取到了好友列表,但是好友列表展示的信息仅仅是用户的账号,感觉不太够意思,这一篇我们将为用户添加电子名片和检测用户的在线状态,以便App能够丰富一些。

    一、电子名片


    电子名片的拉取和好友列表的拉取一样,也是先从本地拉取,如果本地没有就会去服务端拉取并存在本地,所以卸载不卸载App是没关系的,不卸载则拉取本地,卸载了重装会拉取服务端。

    同样的,我们把电子名片相关的类和方法先列在这里,也很少的。

    // 电子名片
    @property (strong, nonatomic) XMPPvCardTemp *vCardTemp;
    /// 电子名片的更新、存储、读取模组
    @property (strong, nonatomic) XMPPvCardTempModule *vCardTempModule;
    /// 电子名片的本地存储
    @property (strong, nonatomic) XMPPvCardCoreDataStorage *vCardCoreDataStorage;
    /// 电子名片的头像模组
    @property (strong, nonatomic) XMPPvCardAvatarModule *vCardAvatarModule;
    
    以下方法的option好像都很简单,什么都没说,于是就那么生用除了很多问题,所以我们可以点进去看方法的实现,看看它们到底做了什么,了解了这几个方法,对我们开发的帮助很大。
    
    /*
     拉取指定联系人的电子名片,不触发回调版:
     1、shouldFetch代表要不要从服务端拉取电子名片。
     2、该方法会优先根据jid读取本地的电子名片并返回,如果在本地没查询到对应jid的电子名片,并且此时我们设置shouldFetch为YES,就会去服务端拉取对应jid的电子名片返回,并存储在本地。但是如果电子名片更新过了,则又会去服务端拉取后返回并存在本地。
     3、此处我们设置shouldFetch为YES,来保证本地没有电子名片时可以去服务端拉取,而不是返回空的电子名片信息。
     */
    - (XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid shouldFetch:(BOOL)shouldFetch;
    
    /*
     拉取指定联系人的电子名片,触发回调版:
     1、ignoreStorage代表要不要忽略本地存储,如果忽略的话,就不会去本地读取电子名片,而是会直接去服务端拉取电子名片并存储在本地。
     2、这个方法并没有什么优先不优先,换句话说不会优先读取本地,要读取服务端还是本地的电子名片完全由我们自己来决定,即通过设置ignoreStorage的值来实现。
     */
    - (void)fetchvCardTempForJID:(XMPPJID *)jid ignoreStorage:(BOOL)ignoreStorage;
    
    
    #pragma mark - XMPPvCardTempModuleDelegate
    
    // 拉取到某个人的电子名片的回调,要么拉取到电子名片,要么拉取到空的电子名片,所以只要调用了带回调拉取电子名片的方法就肯定会触发这个回调,而没有拉取电子名片失败的回调
    - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule 
            didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp 
                         forJID:(XMPPJID *)jid;
    
    
    #pragma mark - XMPPvCardAvatarDelegate
    
    // 拉取到某个人的头像的回调,同上
    - (void)xmppvCardAvatarModule:(XMPPvCardAvatarModule *)vCardTempModule 
                  didReceivePhoto:(UIImage *)photo
                           forJID:(XMPPJID *)jid;
    
    
    
    // 更新自己的电子名片,服务端和本地存储的电子名片都会更新
    - (void)updateMyvCardTemp:(XMPPvCardTemp *)vCardTemp; 
    
    
    #pragma mark - XMPPvCardTempModuleDelegate
    
    // 更新自己电子名片成功的回调
    - (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule;
    
    // 更新自己电子名片失败的回调
    - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error;
    

    那么在一个App的使用中,我们一般会有三个地方用到电子名片:注册时为用户添加电子名片需要显示用户的电子名片时拉取电子名片修改个人信息时更新电子名片。接下来我们分别利用上面的类和方法实现。不过首先,我们现在初始化ProjectXMPP单例时初始化电子名片的相关的东西,并且把电子名片作为UserModel的一个属性。

    -----------ProjectXMPP.m-----------
    
    // 电子名片
    self.vCardCoreDataStorage = [XMPPvCardCoreDataStorage sharedInstance];
    self.vCardTempModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:self.vCardCoreDataStorage];
    [self.vCardTempModule activate:self.stream];
    self.vCardAvatarModule = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:self.vCardTempModule];
    [self.vCardAvatarModule activate:self.stream];
    
    -----------UserModel.h-----------
    
    // 电子名片
    @property (strong, nonatomic) XMPPvCardTemp *vCardTemp;
    
    1、注册时,为用户添加电子名片

    我们在这里修改一下之前的注册界面,添加一个昵称输入框和头像选择来选择bundle里的图片(为了是代码简单点,就不调用相册和相机了)。

    此处我们需要将注册界面作为vCardTempModule的代理,来监测电子名片是否更新成功。

    -----------RegisterViewController.m-----------
    
    [[ProjectXMPP sharedXMPP].vCardTempModule addDelegate:self delegateQueue:dispatch_get_main_queue()];
    

    然后,当我们按下注册按钮的时候,在注册成功的回调里,我们不能直接上传电子名片,会失败的,暂时不知道为啥,在这里需要调用一下登录的方法,而是在登录成功的回调里使用用户填写的昵称和头像构建一个电子名片,并调用更新自己电子名片的方法来设置。

    -----------RegisterViewController.m-----------
    
    #pragma mark - XMPPStreamDelegate
    
    // 注册成功
    - (void)xmppStreamDidRegister:(XMPPStream *)sender {
        
        NSLog(@"===========>注册成功");
        
        [ProjectHUD showMBProgressHUDToView:kWindow withText:@"恭喜你,注册成功,上传电子名片中!" atPosition:(MBProgressHUDTextPositionMiddle) autohideAfter:5 completionHandlerAfterAutohide:^{
            
            // 调用登录,在登录成功的回调里构建电子名片上传,直接在这里上传电子名片不能成功
            [[ProjectXMPP sharedXMPP] loginWithAccount:self.accountTextField.text password:self.passwordTextField.text];
        }];
    }
    
    // 登录成功
    - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
        
        // 创建一个电子名片对象,设置电子名片并上传到openfire服务器
        XMPPvCardTemp *myvCardTemp = [XMPPvCardTemp vCardTemp];
        myvCardTemp.nickname = self.nicknameTextField.text;// 昵称
        myvCardTemp.photo = UIImageJPEGRepresentation(self.headImage, 0.618);// 头像
        [[ProjectXMPP sharedXMPP] updateMyvCardTemp:myvCardTemp];
    }
    
    
    #pragma mark - XMPPvCardTempModuleDelegate
    
    - (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule {
        
        [ProjectHUD showMBProgressHUDToView:kWindow withText:@"电子名片上传成功!" atPosition:(MBProgressHUDTextPositionMiddle) autohideAfter:2 completionHandlerAfterAutohide:^{
            
            [[UIApplication sharedApplication].keyWindow setRootViewController:[[UINavigationController alloc] initWithRootViewController:[[FriendsListViewController alloc] init]]];
            
            // 拉取好友列表
            [[ProjectXMPP sharedXMPP] fetchFriendsList];
        }];
    }
    

    这样我们就完成了为用户设置电子名片,很简单吧,设置完成之后,直接进入App,就开始了下一步:拉取好友列表,显示电子名片。

    2、难点:需要显示用户的电子名片时,拉取电子名片

    之所以说拉取电子名片是重难点,是因为我第一次尝试的时候,出现了各种各样乱七八糟的问题,导致电子名片不能正常的显示,主要是因为对上面拉取电子名片的两套方法没理解清楚,理解清楚了也就没什么难的了。

    现在我们再来回看一下上面两套拉取电子名片的方案,一个带回调,一个不带回调,不带回调的版本会第一次从服务端拉取一下电子名片并持久化在本地,然后以后就只会拉取本地的电子名片了(当然除了我们删掉了本地的电子名片,它会重新读取并持久化);而不带回调的版本则可以由我们来决定到底要读取服务端还是本地的电子名片,所以我们得到拉取电子名片的实现方案,即结合这两套方案来实现,来实现电子名片的拉取,即:通常情况下,我们显示好友信息的时候使用不带回调的版本的方案来优先拉取本地的电子名片显示,这样可以避免一直从服务端拉取数据,而在用户刷新数据好友列表的时候采用带回调的版本从服务端拉取新的数据,这样可以确保好友更改了信息后,通过刷新可以更新数据(并不需要实时的更新,刷新再更新就可以了)。

    接下来我们看一下核心代码,登录成功或注册成功之后我们转战到好友列表界面来拉取电子名片显示,在“获取到一个好友”的回调实现上述方案。

    -----------FriendsListViewController.m-----------
    
    // 获取到一个好友
    - (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item {
        
        NSLog(@"===========>获取到一个好友:%@", item);
        
        // 我们这里只获取双向好友和自己的单向好友
        NSString *subscription = [[item attributeForName:@"subscription"] stringValue];
        if ([subscription isEqualToString:@"both"]) {
        
            // 获取好友的jid
            NSString *friendJidString = [[item attributeForName:@"jid"] stringValue];
            XMPPJID *friendJid = [XMPPJID jidWithString:friendJidString];
            
            if (self.isRefreshing) {// 如果是刷新好友列表,则采用不带回调的版本,在回调里处理
                
                [[ProjectXMPP sharedXMPP] fetchvCardTempWithCallbackForAccount:friendJid.user];
            }else {// 如果是刷新好友列表,通常状况,则采用不带回调的版本,优先拉取用户本地的电子名片
                
                // 构建userModel
                UserModel *tempUser = [[UserModel alloc] init];
                tempUser.jid = friendJid;
                
                XMPPvCardTemp *vCardTemp = [[ProjectXMPP sharedXMPP] fetchvCardTempForAccount:tempUser.jid.user];
                tempUser.vCardTemp = vCardTemp;
                
                // 因为这个代理方法经常会被触发,比如添加、删除好友都会触发这个代理,因此这里就可能对同一个好友拉取多次,所以为了避免好友重复,要判断一下
                for (UserModel *tempUser1 in [ProjectSingleton sharedSingleton].friendsListArray) {
                    
                    if ([tempUser1.jid.user isEqualToString:tempUser.jid.user]) {
                        
                        return;
                    }
                }
                
                // 不存在则添加
                [[ProjectSingleton sharedSingleton].friendsListArray addObject:tempUser];
                [self.tableView reloadData];
            }
        }
        
        // 删除好友
        if ([subscription isEqualToString:@"remove"]) {
            
            // 获取好友的jid
            NSString *friendJidString = [[item attributeForName:@"jid"] stringValue];
            XMPPJID *friendJid = [XMPPJID jidWithString:friendJidString];
            
            // 构建userModel
            UserModel *tempUser = [[UserModel alloc] init];
            tempUser.jid = friendJid;
            
            [[ProjectSingleton sharedSingleton].friendsListArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                
                if ([((UserModel *)obj).jid.user isEqualToString:tempUser.jid.user]) {
                    
                    [[ProjectSingleton sharedSingleton].friendsListArray removeObjectAtIndex:idx];
                    [self.tableView reloadData];
                }
            }];
        }
    }
    
    
    #pragma mark - XMPPvCardTempModuleDelegate
    
    // 拉取到电子名片的回调
    - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule
            didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp
                         forJID:(XMPPJID *)jid {
    
        NSLog(@"===========>获取电子名片成功");
    
        if ([jid.user isEqualToString:[UserModel currentUser].jid.user]) {// 拉取到自己的电子名片
    
    
        }else {// 拉取到好友的电子名片
    
            // 这里代表是刷新,直接替换掉好友的电子名片
            for (UserModel *tempModel in [ProjectSingleton sharedSingleton].friendsListArray) {
    
                if ([tempModel.jid.user isEqualToString:jid.user]) {
    
                    tempModel.vCardTemp = vCardTemp;
                    
                    self.isRefreshing = NO;
                    
                    [self.tableView reloadData];
                }
            }
        }
    }
    

    这样就是实现了电子名片的拉取。

    3、修改个人信息时,更新电子名片

    这里我们新建了一个修改个人信息的界面,进去之后会先拉取自己的电子名片显示,并提供修改电子名片功能。

    修改电子名片其实和注册的时候设置电子名片是一样的,获取到旧电子名片,然后修改电子名片相应的字段,更新电子名片就可以了,不过需要注意的是:更新电子名片的方法,会同时更新自己在服务端和本地存储的电子名片(但是本地更新仅仅是针对自己手机的本地,怎么可能把好友手机的本地也更新了呢🙄,所以自己更新之后,好友的列表里要想更新你的信息,正好我们第2小节实现了刷新功能,刚好能用上)。

    拉取自己的电子名片。

    -----------EditInfoViewController.m-----------
    
    // 拉取我的电子名片
    self.myvCardTemp = [[ProjectXMPP sharedXMPP] fetchvCardTempForAccount:[UserModel currentUser].jid.user];
    
    -----------EditInfoViewController.m-----------
    
    // 更新自己的电子名片,服务器和本地都会更新
    self.myvCardTemp.nickname = self.nicknameTextField.text;// 昵称
    self.myvCardTemp.photo = UIImageJPEGRepresentation(self.headImage, 0.618);// 头像
    [[ProjectXMPP sharedXMPP].vCardTempModule updateMyvCardTemp:self.myvCardTemp];
    

    二、用户上下线状态监测


    用户上下线状态检测的实现可以说非常之简单了,因为只要用户上下线了就会触发一个叫检测好友在线状态的代理方法,之前我们在“拒绝好友请求”的时候用到过,即AppDelegate里的- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence;方法。

    为了记录用户的在线状态,我们为UserModel添加了一个是否在线的BOOL值。

    -----------UserModel.h-----------
    
    // 用户是否在线
    @property (assign, nonatomic) BOOL isAvailable;
    

    我们只需要在“检测好友在线状态”的回调里,获取到上下线的那个好友,并且把他的在线状态改掉,然后发个通知让好友列表界面刷新那条好友记录就可以了。

    -----------AppDelegate.h-----------
    
    // 监测好友在线状态
    // 监测好友在线状态
    - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
        
        NSLog(@"===========>好友:%@,状态:%@", presence.from, presence.type);
        
        // 对方拒绝了我的好友申请时,或者对方删除了我时,我会触发这个回调
        if ([presence.type isEqualToString:@"unsubscribe"]) {
            
            [[ProjectXMPP sharedXMPP] removeFriendWithAccount:presence.from.user];
        }
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            while (1) {// 当用户已经有好友,但是第一次打开App,会现在这里,再走“获取到一个好友的回调”,所以[ProjectSingleton sharedSingleton].friendsListArray还为空,要等到它不为空的时候再做业务
                
                if ([ProjectSingleton sharedSingleton].friendsListArray.count != 0) {
                    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        
                        if ([presence.type isEqualToString:@"available"]) {// 在线
                            
                            [[ProjectSingleton sharedSingleton].friendsListArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                                
                                UserModel *tempUser = ((UserModel *)obj);
                                if ([presence.from.user isEqualToString:tempUser.jid.user]) {
                                    
                                    if (!tempUser.isAvailable) {// 不在线才改为在线,在线的话就不改了,因为这个方法也经常被触发,别老做重复的事
                                        
                                        tempUser.isAvailable = YES;// 修改上下线状态
                                        
                                        // 发送上下线通知
                                        [[NSNotificationCenter defaultCenter] postNotificationName:@"FriendIsAvailable" object:tempUser];
                                    }
                                }
                            }];
                        }
                        
                        if ([presence.type isEqualToString:@"unavailable"]) {// 离线
                            
                            [[ProjectSingleton sharedSingleton].friendsListArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                                
                                UserModel *tempUser = ((UserModel *)obj);
                                if ([presence.from.user isEqualToString:tempUser.jid.user]) {
                                    
                                    if (tempUser.isAvailable) {
                                        
                                        tempUser.isAvailable = NO;
                                        
                                        [[NSNotificationCenter defaultCenter]postNotificationName:@"FriendIsAvailable" object:tempUser];
                                    }
                                }
                            }];
                        }
                    });
                    return;
                }
            }
        });
    }
    

    好友列表界面注册好友上下线通知的观察者。

    -----------FriendsListViewController.h-----------
    
    // 好友上下线的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(friendIsAvailable) name:@"FriendIsAvailable" object:nil];
    
    - (void)friendIsAvailable:(NSNotification *)notification {
        
        [[ProjectSingleton sharedSingleton].friendsListArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            if ([((UserModel *)obj).jid.user isEqualToString:((UserModel *)notification.object).jid.user]) {
                
                [[ProjectSingleton sharedSingleton].friendsListArray replaceObjectAtIndex:idx withObject:notification.object];
                [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:idx inSection:0]] withRowAnimation:(UITableViewRowAnimationNone)];
            }
        }];
    }
    

    好了,上下线检测完事。


    • 综上:
      好友名片必须要用XMPP的吗?不能用我们自己的吗?当然可以直接用我们自己数据库用户表里用户的信息作为好友名片了,这个和好友列表一样,只是XMPP已经写好了现成的接口,可以帮助我们服务端少写接口,我们少和它们对接口而已。比方说接下来的聊天,我们自己服务器上的用户肯定是要导入到openfire服务器指向的数据库的,否则如果一方不在openfire服务器指向的数据库里,根本就找不到这个人,是没法聊天的,但是要不要把咱们服务器数据库用户的电子名片导入到openfire服务器指向的数据库里呢?这个倒不一定要倒了,因为只要你有接口能获取到对方的信息就可以了,但是正如开头所说,那不是还得服务端重新写接口嘛,所以此处我们采取把自己服务器电子名片信息也导入到openfire服务器指向的数据库。

    • 效果:

    1、注册时,为用户添加电子名片 2和4、好友名片的拉取和在线状态的检测 3、修改电子名片及刷新
    上一篇:XMPP实现IM--拉取好友列表、添加删除好友 下一篇:XMPP实现IM--聊天能力的实现

    相关文章

      网友评论

        本文标题:第五篇:XMPP实现IM--电子名片、用户在线状态监测

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