XMPP實現IM(三):電子名片、用戶在線狀態監測

目錄

一、電子名片
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--聊天能力的實現
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容