上帝說:要約炮!于是有了XMPP

一、導(dǎo)入XMPP框架

  1. 下載 XMPPFramework 框架

    GitHub: XMPPFramework

  2. 導(dǎo)入依賴框架

    • CocoaLumberjack : 日志框架

    • CocoaAsyncSocket : 底層網(wǎng)絡(luò)框架
      需要添加 CFNetwork & Security 框架依賴(XCode 6+ 無需導(dǎo)入)

    • KissXML : XML解析框架
      需要添加libxml2.dylib框架依賴
      需要指定如下編譯選項(xiàng):
      Other Linker Flags = -lxml2
      Header Search Paths = /usr/include/libxml2

    • libidn

  3. 導(dǎo)入一下文件夾

    • Authentication
    • Categories
    • Core
    • Utilities
    • 添加依賴:libresolv.dylib
  4. 導(dǎo)入XMPP擴(kuò)展框架

    • Extensions 文件夾
    • 導(dǎo)入 Sample_XMPPFramework.h 并更名為:XMPPFramework.h
    • 添加PCH文件:
    • Add New File -> Other -> PCH文件
    • 添加 #import<UIKit/UIKit.h> 到PCH文件中
    • 設(shè)置編譯選項(xiàng),Build Settings -> Precompile prefix Header 選擇Yes
    • 設(shè)置編譯選項(xiàng),Build Settings -> Prefix Header 設(shè)置PCH文件名:“項(xiàng)目名/“PCH文件名”

二、登錄 & 注銷

實(shí)現(xiàn)用戶登錄的步驟如下:

1. 實(shí)例化XMPPStream并設(shè)置代理,同時(shí)添加代理到工作隊(duì)列      
    
2. 使用JID連接至服務(wù)器,默認(rèn)端口為5222,JID字符串中需要包含服務(wù)器的域名     

3. 在完成連接的代理方法中驗(yàn)證用戶密碼,連接完成后XMPPStream的isConnect屬性為YES     

4. 在驗(yàn)證代理方法中判斷用戶是否登錄成功        

5. 上線或者下線成功后,向服務(wù)器發(fā)送Presence數(shù)據(jù),以更新用戶在服務(wù)器的狀態(tài)     

各部分的實(shí)現(xiàn)代碼如下:

  • 初始化 XMPPStream 并設(shè)置代理:

    -(void)setupXMPPStream{
        
        _xmppStream = [[XMPPStream alloc] init];
        
        // 設(shè)置代理
        [_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    }
    
  • 連接到服務(wù)器

    -(void)connectToHost{
        NSLog(@"開始連接到服務(wù)器");
        if (!_xmppStream) {
            [self setupXMPPStream];
        }
        
        // 設(shè)置登錄用戶JID
        //resource 標(biāo)識用戶登錄的客戶端 iphone android
        
        XMPPJID *myJID = [XMPPJID jidWithUser:@"aaa" domain:@"bourne-mbp.local" resource:@"iphone" ];
        _xmppStream.myJID = myJID;
        
        // 設(shè)置服務(wù)器域名
        _xmppStream.hostName = @"bourne-mbp.local";//不僅可以是域名,還可是IP地址
        
        // 設(shè)置端口 如果服務(wù)器端口是5222,可以省略
        _xmppStream.hostPort = 5222;
        
        // 連接
        NSError *err = nil;
        if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){
            NSLog(@"%@",err);
        }
        
    }
    
  • 連接成功后發(fā)送密碼驗(yàn)證

    -(void)sendPwdToHost{
        NSLog(@"再發(fā)送密碼授權(quán)");
        NSError *err = nil;
        [_xmppStream authenticateWithPassword:@"123456" error:&err];
        if (err) {
            NSLog(@"%@",err);
        }
    }
    
  • 授權(quán)成功后,發(fā)送 在線 消息

    #pragma mark  授權(quán)成功后,發(fā)送"在線" 消息
    -(void)sendOnlineToHost{
        
        NSLog(@"發(fā)送 在線 消息");
        XMPPPresence *presence = [XMPPPresence presence];
        NSLog(@"%@",presence);
        
        [_xmppStream sendElement:presence];
    }
    

需要實(shí)現(xiàn)的幾個(gè)代理方法

#pragma mark 與主機(jī)連接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    NSLog(@"與主機(jī)連接成功");

    // 主機(jī)連接成功后,發(fā)送密碼進(jìn)行授權(quán)
    [self sendPwdToHost];
}

#pragma mark  與主機(jī)斷開連接
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    // 如果有錯(cuò)誤,代表連接失敗
    NSLog(@"與主機(jī)斷開連接 %@",error);
}

#pragma mark 授權(quán)成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    NSLog(@"授權(quán)成功");

    [self sendOnlineToHost];
}

#pragma mark 授權(quán)失敗
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    NSLog(@"授權(quán)失敗 %@",error);
}

注銷登錄

  • 發(fā)送 離線 信息
  • 斷開連接
-(void)logout{
    // 1." 發(fā)送 `離線` 消息"
    XMPPPresence *offline = [XMPPPresence presenceWithType:@"unavailable"];
    [_xmppStream sendElement:offline];
    
    // 2. 與服務(wù)器斷開連接
    [_xmppStream disconnect];
}

三、注冊

  • 與登錄一樣,首先發(fā)送帳號建立連接

  • 連接成功后,發(fā)送注冊的密碼

  • 注冊成功后,框架會通知代理

實(shí)現(xiàn)以下代理方法

- (void)xmppStreamDidRegister:(XMPPStream *)sender {
    NSLog(@"注冊成功");
    
    if (_resultBlock) {
        _resultBlock(BWXMPPLoginResultSuccessed);
    }
}

- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
    if (_resultBlock) {
        _resultBlock(BWXMPPLoginResultFailure);
    }
}

四、用戶信息

XMPP是面向模塊的,每一個(gè)大的動能都屬于某一個(gè)模塊,需要使用時(shí),就在頭文件中將其暴露出來(原本是被注釋了的)。

  1. 工作原理:

    添加用戶信息模塊之后,XMPPFramework框架會自動從服務(wù)器獲取用戶信息,并使用CoreData保存到本地的數(shù)據(jù)庫中,使用XMPPvCardTempModule可以訪問數(shù)據(jù)

  2. XMPPFramework.h中將以下的頭文件前面的注釋去掉:

    // 電子名片模塊
    #import "XMPPvCardTempModule.h"
    #import "XMPPvCardCoreDataStorage.h"
    
    // 頭像模塊
    #import "XMPPvCardAvatarModule.h"
    
  3. 初始化模塊

    //添加電子名片模塊
    _vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
    _vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];
        
    //激活
    [_vCard activate:_xmppStream];
        
    //添加頭像模塊
    _avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];
    [_avatar activate:_xmppStream];
    
  4. 應(yīng)用

    //xmpp提供了一個(gè)方法,直接獲取個(gè)人信息
    XMPPvCardTemp *myVCard =[WCXMPPTool sharedWCXMPPTool].vCard.myvCardTemp;
      
    // 設(shè)置頭像
    if(myVCard.photo){
       self.haedView.image = [UIImage imageWithData:myVCard.photo];
    }
        
    // 設(shè)置昵稱
    self.nicknameLabel.text = myVCard.nickname;
    

五、好友

與用戶信息模塊相似,添加相應(yīng)的好友花名冊模塊即可。

  1. 頭文件

    // 花名冊模塊
    #import "XMPPRoster.h"
    #import "XMPPRosterCoreDataStorage.h"
    
  2. 初始化

    // 添加花名冊模塊【獲取好友列表】
    _rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
    _roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];
    [_roster activate:_xmppStream];
    
  3. 應(yīng)用

    //使用CoreData獲取數(shù)據(jù)
    // 1.上下文【關(guān)聯(lián)到數(shù)據(jù)庫XMPPRoster.sqlite】
    NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].rosterStorage.mainThreadManagedObjectContext;
        
    // 2.FetchRequest【查哪張表】
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPUserCoreDataStorageObject"];
        
    // 3.設(shè)置過濾和排序
    // 過濾當(dāng)前登錄用戶的好友
    NSString *jid = [WCUserInfo sharedWCUserInfo].jid;
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@",jid];
    request.predicate = pre;
        
    //排序
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES];
    request.sortDescriptors = @[sort];
        
    // 4.執(zhí)行請求獲取數(shù)據(jù)
    _resultsContrl = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
        
    _resultsContrl.delegate = self;
        
    NSError *err = nil;
    [_resultsContrl performFetch:&err];
    if (err) {
       WCLog(@"%@",err);
    }
    
    • 注意:使用NSFetchedResultsController并設(shè)置代理,如果數(shù)據(jù)庫的內(nèi)容發(fā)生了變化,這個(gè)類會自動通知代理,就可以設(shè)置界面的數(shù)據(jù),做到實(shí)時(shí)更新。

六、消息

  1. 頭文件

    • 注意:這幾個(gè)頭文件沒在XMPPFramework.h文件中,需要自己添加
    // 消息模塊
    #import "XMPPMessageArchiving.h"
    #import "XMPPMessageArchivingCoreDataStorage.h"
    
  2. 初始化

    // 添加聊天模塊
    _msgStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
    _msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgStorage];
    [_msgArchiving activate:_xmppStream]; 
    
  3. 應(yīng)用

// 上下文
NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].msgStorage.mainThreadManagedObjectContext;
// 請求對象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];

// 過濾、排序
// 1.當(dāng)前登錄用戶的JID的消息
// 2.好友的Jid的消息
NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@ AND bareJidStr = %@",[WCUserInfo sharedWCUserInfo].jid,self.friendJid.bare];
NSLog(@"%@",pre);
request.predicate = pre;

// 時(shí)間升序
NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:YES];
request.sortDescriptors = @[timeSort];

// 查詢
_resultsContr = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

NSError *err = nil;
// 代理
_resultsContr.delegate = self;

[_resultsContr performFetch:&err];

NSLog(@"%@",_resultsContr.fetchedObjects);
if (err) {
    WCLog(@"%@",err);
}
```

七、文件傳送(圖片,音頻)

  1. 原理分析

    • 使用base64將文件轉(zhuǎn)化為字符串,然后再通過XMPPFramework傳輸。
    • 先將文件上傳到服務(wù)器,再將文件網(wǎng)址通過XMPPFramework轉(zhuǎn)輸給好友,好友收到后再自行下載文件。
  1. 難點(diǎn)解析

    • 需要給XMPPFramework的``數(shù)據(jù)體添加一個(gè)信息類型字段。
    XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];
        
    //text 純文本
    //image 圖片
    [msg addAttributeWithName:@"bodyType" stringValue:bodyType];
       
    // 設(shè)置內(nèi)容
    [msg addBody:text];
    NSLog(@"%@",msg);
    [[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
    
    • 根據(jù)消息類型解析消息
    XMPPMessageArchiving_Message_CoreDataObject *msg =  _resultsContr.fetchedObjects[indexPath.row];
    
    
    // 判斷是圖片還是純文本
    NSString *chatType = [msg.message attributeStringValueForName:@"bodyType"];
    if ([chatType isEqualToString:@"image"]) {
        //下圖片顯示
        [cell.imageView sd_setImageWithURL:[NSURL URLWithString:msg.body] placeholderImage:[UIImage imageNamed:@"DefaultProfileHead_qq"]];
        cell.textLabel.text = nil;
    } else if ([chatType isEqualToString:@"text"]){
    
        //顯示消息
        if ([msg.outgoing boolValue]) {//自己發(fā)
            cell.textLabel.text = msg.body;
        }else{//別人發(fā)的
            cell.textLabel.text = msg.body;
        }
        
        cell.imageView.image = nil;
    }
    

聲明

  1. 以上內(nèi)容屬于本人整理的筆記,如有錯(cuò)誤的地方希望能告訴我,大家共同進(jìn)步。

  2. 以上內(nèi)容有些段落或語句可能是本人從其他地方Copy而來,如有侵權(quán),請及時(shí)告訴我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內(nèi)容