華山論劍之淺談XMPP協(xié)議實(shí)現(xiàn)即時(shí)通訊功能

優(yōu)秀的代碼是它自己最好的文檔。當(dāng)你考慮要添加一個(gè)注釋時(shí),問問自己,“如何能改進(jìn)這段代碼,以讓它不需要注釋?”*


XMPP簡(jiǎn)介

XMPP是一種基于標(biāo)準(zhǔn)通用標(biāo)記語言的子集XML的協(xié)議,它繼承了在XML環(huán)境中靈活的發(fā)展性。因此,基于XMPP的應(yīng)用具有超強(qiáng)的可擴(kuò)展性。經(jīng)過擴(kuò)展以后的XMPP可以通過發(fā)送擴(kuò)展的信息來處理用戶的需求,以及在XMPP的頂端建立如內(nèi)容發(fā)布系統(tǒng)和基于地址的服務(wù)等應(yīng)用程序。而且,XMPP包含了針對(duì)服務(wù)器端的軟件協(xié)議,使之能與另一個(gè)進(jìn)行通話,這使得開發(fā)者更容易建立客戶應(yīng)用程序或給一個(gè)配好系統(tǒng)添加功能。


XMPP協(xié)議的實(shí)現(xiàn)原理過程

當(dāng)我們知道XMPP是一種協(xié)議的時(shí)候,我們?nèi)绾瓮ㄟ^objective-c 代碼實(shí)現(xiàn)XMPP協(xié)議,進(jìn)而實(shí)現(xiàn)我們的即時(shí)通訊功能呢? 下面的圖片就是為我們做了很好的解釋.


XMPP協(xié)議的代碼實(shí)現(xiàn)

1.準(zhǔn)備工作

我們做的客戶端也服務(wù)器通訊通道的實(shí)現(xiàn)以及數(shù)據(jù)交流,那么首先要有我們自己的服務(wù)器,當(dāng)然了,在公司的好說一些,如果是個(gè)人研究技術(shù)怎么辦呢?我們可以自己搭建一個(gè)服務(wù)器或者使用leancloud這種第三方服務(wù)器,這里我給大家提供一些搭建服務(wù)器的工具,當(dāng)然了,自己搭建的服務(wù)器生命比較脆弱,請(qǐng)大家好好愛護(hù)~還有就是leancloud也是我推薦的一種方法.

----->點(diǎn)擊前往LeanCloud官方網(wǎng)站
----->XMPP本地服務(wù)器搭建工具下載
2.OC搭建Client和連接通道
工程完成目標(biāo):
 1.創(chuàng)建通訊通道并完成賬號(hào)密碼的登錄
 2.創(chuàng)建通道并完成賬號(hào)的申請(qǐng)
 3.好友列表的獲取和顯示
 4.即時(shí)通訊功能的實(shí)現(xiàn)

首先,我們需要導(dǎo)入我們的所需要導(dǎo)入的XMPPFramework(PS:點(diǎn)擊打開下載,完成之后直接解壓拖到工程中??),還有手動(dòng)的導(dǎo)入兩個(gè)庫(kù).如下.

libxml2.tbd
libresolv.tbd

然后,我們就要配置我們的build setting頁面的設(shè)置 search paths 選添加一個(gè)字段,添加如下

/usr/include/libxml2

完成上面的設(shè)置之后 我們需要?jiǎng)?chuàng)建一個(gè)單例類XMPPManager,用它來創(chuàng)建通訊通道實(shí)現(xiàn)上面的四個(gè)功能.

XMPPManager.h中如下

#import <Foundation/Foundation.h>

#import "XMPPFramework.h"


@interface XMPPManager : NSObject

//通訊管道
@property(nonatomic,strong)XMPPStream *stream;

//和通訊錄對(duì)象很像,用來管理好友類~
@property(nonatomic,strong)XMPPRoster *roster;

//XMPP聊天消息本地化處理對(duì)象
@property(nonatomic,strong)XMPPMessageArchiving *messageArchiving;

//消息上下文對(duì)象
@property(nonatomic,strong)NSManagedObjectContext *messageContext;

+(instancetype)defaulManager;

-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password;

-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password;


//與服務(wù)器斷開鏈接
-(void)disconnectWithServer;

@end

XMPPManager.h文件的解釋:
stream : 這是C和S之間的通訊通道.
roster : 這個(gè)屬性管理好友列表的一個(gè)屬性.
messageArchiving : 這個(gè)屬性用來管理本地聊天記錄的一個(gè)類
messageContext : 消息上下文對(duì)象.
+(instancetype)defaulManager:創(chuàng)建單例的方法
-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password : 登錄的方法
-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password : 注冊(cè)新賬號(hào)的方法
-(void)disconnectWithServer; 與服務(wù)器斷開鏈接



了解完各個(gè)方法之后,我們就要在XMPPManager.m實(shí)現(xiàn)一下,實(shí)現(xiàn)如下


#import "XMPPManager.h"

//代表與服務(wù)器進(jìn)行連接的類型.
typedef enum : NSUInteger {
    DoLgin,
    DORegiser,
} ConnetType;



@interface XMPPManager()<XMPPStreamDelegate,XMPPRosterDelegate,XMPPMessageArchivingStorage>

@property(nonatomic,strong)NSString *password;

@property(nonatomic,strong)NSString *regiserPassword;

//聲明一個(gè)屬性 記錄連接的類型
@property(nonatomic,assign)ConnetType type;

@end



@implementation XMPPManager

static XMPPManager *manager;

+(instancetype)defaulManager{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        manager  = [[XMPPManager alloc]init];
        
    });
    
    return manager;

}


-(instancetype)init{

    if (self = [super init]) {
        
        self.stream = [[XMPPStream alloc]init];
        self.stream.hostName = kHostName;
        self.stream.hostPort = kHostPort;
        //設(shè)置stream的代理
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        
        //下面這一堆其實(shí)是對(duì)roster對(duì)象進(jìn)行初始化.
        //系統(tǒng)寫好的XMPP存儲(chǔ)對(duì)象
        XMPPRosterCoreDataStorage *dataStorage = [XMPPRosterCoreDataStorage sharedInstance];
        
        self.roster = [[XMPPRoster alloc]initWithRosterStorage:dataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
        
        //激活roster
        [self.roster activate:self.stream];
        
        //給roster對(duì)象指定代理
        [self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        //初始化聊天記錄管理對(duì)象
        
        XMPPMessageArchivingCoreDataStorage  *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
        
        self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
        
        //激活管理對(duì)象
        [self.messageArchiving activate:self.stream];
        
        //設(shè)置管理對(duì)象代理
        [self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
        
    }

    return self;

}


//與服務(wù)器的建立鏈接
-(void)connectToServerWintUser:(NSString *)name{

    if ([self.stream isConnected]) {
        
        [self.stream disconnect];
    
    }
    
    //jid jabberID,是基于jabber協(xié)議的由用戶名生成的唯一ID
    self.stream.myJID = [XMPPJID jidWithUser:name domain:kDomin resource:kResource];
    
    NSError *error = nil;
    
    //與服務(wù)器建立鏈接.
    [self.stream connectWithTimeout:30.0f error:&error];
    
    if (error != nil) {
        
        @throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務(wù)器建立連接失敗,請(qǐng)查看代碼" userInfo:nil];
        
    }

    
}


//與服務(wù)器斷開鏈接
-(void)disconnectWithServer{
    
    [self.stream disconnect];
    
}

-(void)LoginWithUserName:(NSString *)name AndPassWord:(NSString *)password{

    self.password = password;
    
    self.type = DoLgin;

    [self connectToServerWintUser:name];

}

//與服務(wù)器建立連接
-(void)xmppStreamDidConnect:(XMPPStream *)sender{

    NSLog(@"與服務(wù)器建立鏈接正常");
    //與服務(wù)器進(jìn)行登錄認(rèn)證
    NSError *error = nil;
    
    switch (self.type) {
        case DoLgin:
            [self.stream authenticateWithPassword:self.password error:&error];
            
            
            if (error != nil) {
                
                NSLog(@"認(rèn)證過程出錯(cuò)!");
                
            }
            break;
            
        case DORegiser:
            
            [self.stream registerWithPassword:self.regiserPassword error:&error];
            
            if (error != nil) {
                
                NSLog(@"注冊(cè)過程出錯(cuò)!");
                
            }
            break;
            
            
            
        default:
            break;
    }

}

-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{

@throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務(wù)器建立連接超時(shí),請(qǐng)查看代碼" userInfo:nil];

}

-(void)RegiserWithUserName:(NSString *)name AndPassWord:(NSString *)password{

    
    self.type = DORegiser;
    
    self.regiserPassword = password;
    
    [self connectToServerWintUser:name];
    
}

看完了上面的代碼,連我自己都覺得亂亂的,所以 我們一個(gè)功能一個(gè)功能看這些代碼的實(shí)現(xiàn)原理,


登錄功能


我們想要登錄我們的服務(wù)器,首先要有我們的賬號(hào)和密碼,然后我們就需要建立通道

(a) defaulManager

創(chuàng)建單例這個(gè)方法中就是創(chuàng)建了我們的單例.

(b) init

初始化這個(gè)方法中我們需要對(duì)我們的通訊管道屬性stream進(jìn)行初始化一下,設(shè)置stream服務(wù)器IP地址和服務(wù)器端口,還有就是設(shè)置stream的代理對(duì)象.實(shí)現(xiàn)XMPPStreamDelegate協(xié)議方法.這里設(shè)置代理對(duì)象的方法不同于以前,這里是使用runtime設(shè)計(jì)模式可以為stream設(shè)置多個(gè)代理對(duì)象.

(c) LoginWithUserName:(NSString )name AndPassWord:(NSString )password

這個(gè)方法中首先我們需要保存我們的密碼,用于傳值到下一個(gè)方法中.self.type = DoLgin;這句代碼有作何解釋呢?因?yàn)椴还苁堑卿浐妥?cè),我們都要與我們的服務(wù)器創(chuàng)建聯(lián)系,那么服務(wù)器是如何知道我們是創(chuàng)建的什么聯(lián)系的呢?就是通過這句代碼實(shí)現(xiàn)的,當(dāng)然了,現(xiàn)在你可能聽得糊涂,當(dāng)看到下面的方法的時(shí)候你就明白了.[self connectToServerWintUser:name];這句代碼就是要?jiǎng)?chuàng)建于服務(wù)器之間的聯(lián)系.這時(shí)候,賬號(hào)name就通過參數(shù)的形式傳到了connectToServerWintUser這個(gè)函數(shù),而password通過屬性的傳值到xmppStreamDidConnect(當(dāng)完成通道的建立的時(shí)候執(zhí)行的代理方法).

(d) connectToServerWintUser

[self.stream disconnect];這句代碼就是讓客戶端斷開連接通道,綜合上面來看,當(dāng)我們已經(jīng)存在的連接的通道的時(shí)候,我們就會(huì)讓通道斷開,這是為什么呢?因?yàn)镃與S之間的通道只能創(chuàng)建一條,當(dāng)我們重復(fù)創(chuàng)建的時(shí)候,就會(huì)導(dǎo)致我們的程序崩潰.所以我們要保障我們的通道是只有一條的. [self.stream connectWithTimeout:30.0f error:&error];這句代碼就是我們創(chuàng)建通道,當(dāng)然了等待時(shí)間是30秒.

(e)xmppStreamDidConnect

這是一個(gè)代理的方法,當(dāng)我們完成通道的創(chuàng)建之后,我們就會(huì)調(diào)用這個(gè)方法,在這個(gè)代理方法中我們需要做的就是對(duì)我們的密碼進(jìn)行驗(yàn)證.[self.stream authenticateWithPassword:self.password error:&error];這就是登錄驗(yàn)證我們的密碼.驗(yàn)證成功之后就會(huì)調(diào)用-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender這個(gè)代理方法,驗(yàn)證失敗就會(huì)調(diào)用-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error 這個(gè)代理方法,當(dāng)然了,這兩個(gè)方法是寫在我們的登錄頁面的,因?yàn)槲覀冃枰獙?duì)使用者有個(gè)用戶的交互不是?比如彈出一個(gè)彈窗.提醒一下用戶.xmppStreamDidConnect不管是注冊(cè)和登錄都會(huì)調(diào)用,我們?cè)趺磪^(qū)分呢?我們現(xiàn)在.m文件的頂部設(shè)置了一個(gè)枚舉值,通過枚舉值的值判斷我們所需要的操作.

(f)xmppStreamConnectDidTimeout

這個(gè)方法就是說,當(dāng)我們與服務(wù)器建立連接超時(shí)的時(shí)候會(huì)進(jìn)行的操作.@throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務(wù)器建立連接超時(shí),請(qǐng)查看代碼" userInfo:nil];是我們手動(dòng)的拋出一個(gè)異常.



注冊(cè)功能


注冊(cè)功能與登錄功能在實(shí)現(xiàn)上是相似的,下面的屬性就是區(qū)別的開始.

@property(nonatomic,strong)NSString *password;

@property(nonatomic,strong)NSString *regiserPassword;

xmppStreamDidConnect
在這個(gè)方法中,我們需要對(duì)我們的注冊(cè)方法與登錄方法分別開來. [self.stream registerWithPassword:self.regiserPassword error:&error];這個(gè)方法就是我們把注冊(cè)的密碼傳到服務(wù)器上保存的方法.當(dāng)然了,當(dāng)我們注冊(cè)成功的時(shí)候,就會(huì)調(diào)動(dòng)-(void)xmppStreamDidRegister:(XMPPStream *)sender這個(gè)協(xié)議方法,當(dāng)注冊(cè)不成功的時(shí)候,我們就會(huì)調(diào)用-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error這個(gè)方法.這里我們也要拍給我們的用戶一些交互,讓我們用戶知道自己注冊(cè)的結(jié)果.


好友列表功能


在XMPPManager.m文件中,我們需要做的就是在 *** init *** 方法中對(duì)管理好友列表的roster對(duì)象進(jìn)行一下初始化并且制定代理,這里需要注意一個(gè)地方,當(dāng)我們?cè)O(shè)置roster的初始化的時(shí)候,我們需要使用 *** dispatch_get_global_queue(0, 0) ***全局線程,不能使用主線程,原因是如果使用主線程會(huì)出現(xiàn)一些莫名其妙的Bug. [self.roster activate:self.stream]; 激活roster的意思就是給roster可以通過stream通道的權(quán)限..(PS:大白話 ??)

在我們的好友列表頁面中,首先我們需要確定他是一個(gè)UITableViewController,然后我們需要從我們的服務(wù)器拿到我們的好友的列表數(shù)組.通道stream連接在我們的登錄的時(shí)候已經(jīng)完成了,所以我們不需要再管理通道了,我們需要在好友列表的控制器中實(shí)現(xiàn)XMPPRosterDelegate的代理方法來獲取到我們的好友列表.代碼如下

在MainTableViewController.m中

#import "MainTableViewController.h"

#import "XMPPManager.h"

#import "ChatTableViewController.h"

@interface MainTableViewController ()<XMPPRosterDelegate>

//用來存儲(chǔ)所有的好友信息的.
@property(nonatomic,strong)NSMutableArray *dataArray;

@end

@implementation MainTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.dataArray = [NSMutableArray array];
    
    
    [[XMPPManager defaulManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

    }


//roster代理方法
//開始獲取好友列表的時(shí)候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{

    NSLog(@"開始獲取好友列表");

    

}

//結(jié)束獲取好友列表
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{

    NSLog(@"獲取好友列表完成的時(shí)候.");

}

//獲取好友信息的時(shí)候
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{

//    //將每一個(gè)好友存儲(chǔ)下來.
//    NSLog(@"%@",[item children]);
//    
//    NSLog(@"%@",[item name]);
    
    NSString *SJid = [[item attributeForName:@"jid"] stringValue];
    
    //把字符串類型的JID轉(zhuǎn)換成XMPPJID
    XMPPJID *jid = [XMPPJID jidWithString:SJid];
    
    //把JID存儲(chǔ)到數(shù)組中,相當(dāng)修改數(shù)據(jù)源
    [self.dataArray addObject:jid];
    
    //更新UI
    NSIndexPath *path = [NSIndexPath indexPathForRow:self.dataArray.count-1 inSection:0];
    
    [self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom];
    

}





#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataArray.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MainCell" forIndexPath:indexPath];
    
    
    //把字符串類型的JID轉(zhuǎn)換成XMPPJID
    XMPPJID *jid = self.dataArray[indexPath.row];
    
    
    cell.textLabel.text = [NSString stringWithFormat:@"%@    %@     %@",jid.user,jid.resource,jid.domain];
    
    return cell;
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

    UITableViewCell *cell = (UITableViewCell *)sender;
    
    //拿到下一步要跳轉(zhuǎn)的controller對(duì)象
    ChatTableViewController *chatVC = segue.destinationViewController;
    
    //判斷選中的cell在當(dāng)前的Table中的位置
    NSIndexPath *path = [self.tableView indexPathForCell:cell];
    
    chatVC.chatToJID = self.dataArray[path.row];


}


@end

方法解釋

在上面的代碼中我們要解釋的只有兩個(gè)方法 ,一個(gè)是獲取好友信息的時(shí)候調(diào)用的

-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item

另外一個(gè)是我們點(diǎn)擊好友進(jìn)入聊天頁面所需要的方法.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item

這個(gè)是XMPPRosterDelegate協(xié)議中代理方法,如果我們有很多的好友,這個(gè)方法會(huì)調(diào)用很多次,知道我們的好友全部遍歷完. NSString *SJid = [[item attributeForName:@"jid"] stringValue]; 和XMPPJID *jid = [XMPPJID jidWithString:SJid];這兩個(gè)方法就是當(dāng)我們從服務(wù)器接到我們的數(shù)據(jù)時(shí)候,我們要先將他轉(zhuǎn)化成一下,轉(zhuǎn)成成我們所需要的數(shù)據(jù),然后存入我們的數(shù)據(jù)源數(shù)組中.更新UI 使用的方法是[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom]; 為什么使用這個(gè)方法呢?為什么不使用reloadData這個(gè)方法?因?yàn)檫@個(gè)代理方法會(huì)執(zhí)行很多次,我們?yōu)榱吮苊鉀]有必要的內(nèi)存負(fù)擔(dān),所以我們只需要更新一下我們最后一條數(shù)據(jù)就行,這樣大大減少了內(nèi)存的負(fù)擔(dān),提高了我們的工程效率.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

這個(gè)方法是因?yàn)槲沂褂胹toryboard的原因,我們?yōu)榱四苷_找到我們點(diǎn)擊對(duì)應(yīng)的cell所使用的方法.傳值的時(shí)候我們需要把JID傳到聊天界面,這樣服務(wù)器就會(huì)清楚的知道我們是對(duì)誰進(jìn)行聊天了.



聊天界面功能


對(duì)于聊天界面的搭建,我們也是使用到UITableViewController,邏輯是我們需要往服務(wù)器發(fā)送我們的消息,然后服務(wù)器在通過JID發(fā)送到指定的消息,發(fā)送者發(fā)送消息的時(shí)候和接收者接收到消息的時(shí)候刷新我們的UI.

那我們就先看看在XMPPManager的類中我們需要做一些什么事情吧.
在XMPPManager.h中 我們創(chuàng)建了兩個(gè)屬性,一個(gè)是XMPP聊天消息本地化處理對(duì)象的messageArchiving,另外一個(gè)是消息上下文對(duì)象messageContext.
在XMPPManager.m中 的init方法中,我們對(duì)這兩個(gè)屬性進(jìn)行了初始化.如下

//初始化聊天記錄管理對(duì)象
        
        XMPPMessageArchivingCoreDataStorage  *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
        
        self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
        
        //激活管理對(duì)象
        [self.messageArchiving activate:self.stream];
        
        //設(shè)置管理對(duì)象代理
        [self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
        

messageArchiving對(duì)象也是需要我們激活通道權(quán)限的.然后設(shè)置了代理.

在ChatTableViewController.h界面 我們需要設(shè)置一個(gè)XMPPJID對(duì)象 來接受好友列表傳來的JID值.代碼如下.

#import <UIKit/UIKit.h>

#import "XMPPManager.h"

@interface ChatTableViewController : UITableViewController

//接收好友列表傳來的JID
@property(nonatomic,strong)XMPPJID *chatToJID;

@end

在ChatTableViewController.m文件中,我們需要做的就是實(shí)現(xiàn)XMPPStreamDelegate的代理方法,從代理方法中實(shí)現(xiàn)往服務(wù)器發(fā)送數(shù)據(jù)和從服務(wù)器接收數(shù)據(jù)的操作.代碼如下


#import "ChatTableViewController.h"

@interface ChatTableViewController ()<XMPPStreamDelegate>

//存放所有的消息
@property(nonatomic,strong)NSMutableArray *messageArray;

@end

@implementation ChatTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.messageArray = [NSMutableArray array];
    
    //添加代理
    [[XMPPManager defaulManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    
    //更新聊天記錄信息
    [self reloadMessage];
    
    
    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
    
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

//展現(xiàn)聊天記錄
-(void)reloadMessage{

    NSManagedObjectContext *context = [XMPPManager defaulManager].messageContext;
    
#pragma mark----直接一個(gè) fet 下面全都出來了----
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    //這里面要填的是XMPPARChiver的coreData實(shí)例類型
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    
    //對(duì)取到的數(shù)據(jù)進(jìn)行過濾,傳入過濾條件.
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@", [XMPPManager defaulManager].stream.myJID.bare,self.chatToJID.bare];
    [fetchRequest setPredicate:predicate];
    // Specify how the fetched objects should be sorted
    
    //設(shè)置排序的關(guān)鍵字
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp"
                                                                   ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
    
    NSError *error = nil;
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
    if (fetchedObjects == nil) {

        //完成之后干什么?
        NSLog(@"和此人的激情交談");
    
    }

    /********獲取和這個(gè)人所有的聊天記錄***************/
    
    //清空聊天數(shù)組中的消息
    [self.messageArray removeAllObjects];
    
    //將新的聊天記錄添加到數(shù)組中
    self.messageArray = [NSMutableArray arrayWithArray:fetchedObjects];
    
    NSLog(@"%ld",self.messageArray.count);
    
    //刷新UI
    [self.tableView reloadData];
    
    //將tableview直接滑動(dòng)到最底部
    NSIndexPath * indexpath = [NSIndexPath indexPathForRow: self.messageArray.count-1 inSection:0];
    
    if (indexpath.row > 0) {
        
        [self.tableView selectRowAtIndexPath:indexpath animated:YES scrollPosition:UITableViewScrollPositionBottom];
        
    }
    
    
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.messageArray.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ChatCell" forIndexPath:indexPath];
    
    
    //取到我們對(duì)應(yīng)的信息
    XMPPMessageArchiving_Message_CoreDataObject *message = self.messageArray[indexPath.row];
    
    if (message.isOutgoing == YES) {
        
        cell.detailTextLabel.text  = message.body;
        
        cell.textLabel.text = @"";
        
    }else {
    
        cell.textLabel.text = message.body;
        
        cell.detailTextLabel.text = @"";
    
    }

    
    
    return cell;
}

//發(fā)送消息
- (IBAction)sendAction:(id)sender {
    
    
    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJID];
    
    [message addBody:@"nice to meet you"];
    
    //發(fā)送消息
    [[XMPPManager defaulManager].stream sendElement:message];
    
    
    
}

-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{

//    tipWithMessage(@"消息發(fā)送成功!");
    
    [self reloadMessage];


}


-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error{

    tipWithMessage(@"消息發(fā)送失敗");

    
}


-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
    
    [self reloadMessage];

}

@end




頁面的邏輯:

當(dāng)我們進(jìn)入聊天界面的時(shí)候,我們會(huì)先調(diào)用reloadMessage這個(gè)方法,從服務(wù)器下載當(dāng)前用戶與這個(gè)JID的聊天記錄,當(dāng)我們點(diǎn)擊發(fā)送消息的時(shí)候,我們就會(huì)調(diào)用sendAction這個(gè)方法,發(fā)送到服務(wù)器上去,當(dāng)我們發(fā)送成功后調(diào)用didSendMessage這個(gè)協(xié)議方法,然后在這協(xié)議方法中調(diào)用reloadMessage這個(gè)方法重新從服務(wù)器下載新的聊天記錄并且刷新UI,然后服務(wù)器上有當(dāng)前用戶新的消息的時(shí)候,就會(huì)調(diào)用didReceiveMessage這個(gè)代理方法,我們只需要在這個(gè)方法中再次調(diào)用reloadMessage方法就可.(PS:因?yàn)槲矣玫氖莝toryboard 所有有些地方會(huì)有所不同??)



方法解釋:


-(void)reloadMessage

這個(gè)方法是整個(gè)聊天界面的核心.我們?cè)谶@里面做的就是從服務(wù)器下載我們的數(shù)據(jù).當(dāng)我們下載完數(shù)據(jù)的時(shí)候,就要把我們數(shù)據(jù)源數(shù)組中所有的元素清空,然后將所有從服務(wù)器中下載的元素添加到我們的數(shù)組中.然后刷新我們的UI,當(dāng)我一刷新之后tableView就會(huì)重頭開始了,所以我們?cè)O(shè)置讓最后一個(gè)cell顯示在屏幕上.還有就是下面的一段代碼,這是我們先前寫好的,我們需要敲出 *** fetch *** 就會(huì)給我們提示,是不是很簡(jiǎn)答??? 這段代碼就是從網(wǎng)上下載我們所需要的數(shù)據(jù).當(dāng)然了,我們需要使用謂詞對(duì)這些數(shù)據(jù)進(jìn)行過濾.過濾后的數(shù)據(jù)才是我們所需要的數(shù)據(jù).

 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    //這里面要填的是XMPPARChiver的coreData實(shí)例類型
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    
    //對(duì)取到的數(shù)據(jù)進(jìn)行過濾,傳入過濾條件.
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@", [XMPPManager defaulManager].stream.myJID.bare,self.chatToJID.bare];
    [fetchRequest setPredicate:predicate];
    // Specify how the fetched objects should be sorted
    
    //設(shè)置排序的關(guān)鍵字
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp"
                                                                   ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
    
    NSError *error = nil;
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
    if (fetchedObjects == nil) {

        //完成之后干什么?
        NSLog(@"和此人的激情交談");
    
    }


XMPP協(xié)議實(shí)現(xiàn)即時(shí)通訊的過程大體就是這樣了,當(dāng)然了,我還有一些功能沒有完善,比如添加好友的功能.這將在后期進(jìn)行再次的完善.謝謝大家的查看..
--->XMPP的Demo資源下載
在Demo拿到手的時(shí)候,我們首先要對(duì)我們的服務(wù)器IP進(jìn)行設(shè)置.在XMPPConfig.h文件中設(shè)置服務(wù)器相關(guān)信息!!!


如果有有什么疑問,可以回復(fù),謝謝大家
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容