華山論劍之淺談XMPP協議實現即時通訊功能

優秀的代碼是它自己最好的文檔。當你考慮要添加一個注釋時,問問自己,“如何能改進這段代碼,以讓它不需要注釋?”*


XMPP簡介

XMPP是一種基于標準通用標記語言的子集XML的協議,它繼承了在XML環境中靈活的發展性。因此,基于XMPP的應用具有超強的可擴展性。經過擴展以后的XMPP可以通過發送擴展的信息來處理用戶的需求,以及在XMPP的頂端建立如內容發布系統和基于地址的服務等應用程序。而且,XMPP包含了針對服務器端的軟件協議,使之能與另一個進行通話,這使得開發者更容易建立客戶應用程序或給一個配好系統添加功能。


XMPP協議的實現原理過程

當我們知道XMPP是一種協議的時候,我們如何通過objective-c 代碼實現XMPP協議,進而實現我們的即時通訊功能呢? 下面的圖片就是為我們做了很好的解釋.


XMPP協議的代碼實現

1.準備工作

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

----->點擊前往LeanCloud官方網站
----->XMPP本地服務器搭建工具下載
2.OC搭建Client和連接通道
工程完成目標:
 1.創建通訊通道并完成賬號密碼的登錄
 2.創建通道并完成賬號的申請
 3.好友列表的獲取和顯示
 4.即時通訊功能的實現

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

libxml2.tbd
libresolv.tbd

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

/usr/include/libxml2

完成上面的設置之后 我們需要創建一個單例類XMPPManager,用它來創建通訊通道實現上面的四個功能.

XMPPManager.h中如下

#import <Foundation/Foundation.h>

#import "XMPPFramework.h"


@interface XMPPManager : NSObject

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

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

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

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

+(instancetype)defaulManager;

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

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


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

@end

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



了解完各個方法之后,我們就要在XMPPManager.m實現一下,實現如下


#import "XMPPManager.h"

//代表與服務器進行連接的類型.
typedef enum : NSUInteger {
    DoLgin,
    DORegiser,
} ConnetType;



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

@property(nonatomic,strong)NSString *password;

@property(nonatomic,strong)NSString *regiserPassword;

//聲明一個屬性 記錄連接的類型
@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;
        //設置stream的代理
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        
        //下面這一堆其實是對roster對象進行初始化.
        //系統寫好的XMPP存儲對象
        XMPPRosterCoreDataStorage *dataStorage = [XMPPRosterCoreDataStorage sharedInstance];
        
        self.roster = [[XMPPRoster alloc]initWithRosterStorage:dataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
        
        //激活roster
        [self.roster activate:self.stream];
        
        //給roster對象指定代理
        [self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        //初始化聊天記錄管理對象
        
        XMPPMessageArchivingCoreDataStorage  *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
        
        self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
        
        //激活管理對象
        [self.messageArchiving activate:self.stream];
        
        //設置管理對象代理
        [self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
        
    }

    return self;

}


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

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

    
}


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

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

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

    [self connectToServerWintUser:name];

}

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

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

}

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

@throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務器建立連接超時,請查看代碼" userInfo:nil];

}

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

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

看完了上面的代碼,連我自己都覺得亂亂的,所以 我們一個功能一個功能看這些代碼的實現原理,


登錄功能


我們想要登錄我們的服務器,首先要有我們的賬號和密碼,然后我們就需要建立通道

(a) defaulManager

創建單例這個方法中就是創建了我們的單例.

(b) init

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

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

這個方法中首先我們需要保存我們的密碼,用于傳值到下一個方法中.self.type = DoLgin;這句代碼有作何解釋呢?因為不管是登錄和注冊,我們都要與我們的服務器創建聯系,那么服務器是如何知道我們是創建的什么聯系的呢?就是通過這句代碼實現的,當然了,現在你可能聽得糊涂,當看到下面的方法的時候你就明白了.[self connectToServerWintUser:name];這句代碼就是要創建于服務器之間的聯系.這時候,賬號name就通過參數的形式傳到了connectToServerWintUser這個函數,而password通過屬性的傳值到xmppStreamDidConnect(當完成通道的建立的時候執行的代理方法).

(d) connectToServerWintUser

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

(e)xmppStreamDidConnect

這是一個代理的方法,當我們完成通道的創建之后,我們就會調用這個方法,在這個代理方法中我們需要做的就是對我們的密碼進行驗證.[self.stream authenticateWithPassword:self.password error:&error];這就是登錄驗證我們的密碼.驗證成功之后就會調用-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender這個代理方法,驗證失敗就會調用-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error 這個代理方法,當然了,這兩個方法是寫在我們的登錄頁面的,因為我們需要對使用者有個用戶的交互不是?比如彈出一個彈窗.提醒一下用戶.xmppStreamDidConnect不管是注冊和登錄都會調用,我們怎么區分呢?我們現在.m文件的頂部設置了一個枚舉值,通過枚舉值的值判斷我們所需要的操作.

(f)xmppStreamConnectDidTimeout

這個方法就是說,當我們與服務器建立連接超時的時候會進行的操作.@throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務器建立連接超時,請查看代碼" userInfo:nil];是我們手動的拋出一個異常.



注冊功能


注冊功能與登錄功能在實現上是相似的,下面的屬性就是區別的開始.

@property(nonatomic,strong)NSString *password;

@property(nonatomic,strong)NSString *regiserPassword;

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


好友列表功能


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

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

在MainTableViewController.m中

#import "MainTableViewController.h"

#import "XMPPManager.h"

#import "ChatTableViewController.h"

@interface MainTableViewController ()<XMPPRosterDelegate>

//用來存儲所有的好友信息的.
@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代理方法
//開始獲取好友列表的時候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{

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

    

}

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

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

}

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

//    //將每一個好友存儲下來.
//    NSLog(@"%@",[item children]);
//    
//    NSLog(@"%@",[item name]);
    
    NSString *SJid = [[item attributeForName:@"jid"] stringValue];
    
    //把字符串類型的JID轉換成XMPPJID
    XMPPJID *jid = [XMPPJID jidWithString:SJid];
    
    //把JID存儲到數組中,相當修改數據源
    [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轉換成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;
    
    //拿到下一步要跳轉的controller對象
    ChatTableViewController *chatVC = segue.destinationViewController;
    
    //判斷選中的cell在當前的Table中的位置
    NSIndexPath *path = [self.tableView indexPathForCell:cell];
    
    chatVC.chatToJID = self.dataArray[path.row];


}


@end

方法解釋

在上面的代碼中我們要解釋的只有兩個方法 ,一個是獲取好友信息的時候調用的

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

另外一個是我們點擊好友進入聊天頁面所需要的方法.

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

這個是XMPPRosterDelegate協議中代理方法,如果我們有很多的好友,這個方法會調用很多次,知道我們的好友全部遍歷完. NSString *SJid = [[item attributeForName:@"jid"] stringValue]; 和XMPPJID *jid = [XMPPJID jidWithString:SJid];這兩個方法就是當我們從服務器接到我們的數據時候,我們要先將他轉化成一下,轉成成我們所需要的數據,然后存入我們的數據源數組中.更新UI 使用的方法是[self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom]; 為什么使用這個方法呢?為什么不使用reloadData這個方法?因為這個代理方法會執行很多次,我們為了避免沒有必要的內存負擔,所以我們只需要更新一下我們最后一條數據就行,這樣大大減少了內存的負擔,提高了我們的工程效率.

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

這個方法是因為我使用storyboard的原因,我們為了能正確找到我們點擊對應的cell所使用的方法.傳值的時候我們需要把JID傳到聊天界面,這樣服務器就會清楚的知道我們是對誰進行聊天了.



聊天界面功能


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

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

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

messageArchiving對象也是需要我們激活通道權限的.然后設置了代理.

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

#import <UIKit/UIKit.h>

#import "XMPPManager.h"

@interface ChatTableViewController : UITableViewController

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

@end

在ChatTableViewController.m文件中,我們需要做的就是實現XMPPStreamDelegate的代理方法,從代理方法中實現往服務器發送數據和從服務器接收數據的操作.代碼如下


#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;
}

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

    NSManagedObjectContext *context = [XMPPManager defaulManager].messageContext;
    
#pragma mark----直接一個 fet 下面全都出來了----
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    //這里面要填的是XMPPARChiver的coreData實例類型
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    
    //對取到的數據進行過濾,傳入過濾條件.
    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
    
    //設置排序的關鍵字
    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(@"和此人的激情交談");
    
    }

    /********獲取和這個人所有的聊天記錄***************/
    
    //清空聊天數組中的消息
    [self.messageArray removeAllObjects];
    
    //將新的聊天記錄添加到數組中
    self.messageArray = [NSMutableArray arrayWithArray:fetchedObjects];
    
    NSLog(@"%ld",self.messageArray.count);
    
    //刷新UI
    [self.tableView reloadData];
    
    //將tableview直接滑動到最底部
    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];
    
    
    //取到我們對應的信息
    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;
}

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

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

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


}


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

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

    
}


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

}

@end




頁面的邏輯:

當我們進入聊天界面的時候,我們會先調用reloadMessage這個方法,從服務器下載當前用戶與這個JID的聊天記錄,當我們點擊發送消息的時候,我們就會調用sendAction這個方法,發送到服務器上去,當我們發送成功后調用didSendMessage這個協議方法,然后在這協議方法中調用reloadMessage這個方法重新從服務器下載新的聊天記錄并且刷新UI,然后服務器上有當前用戶新的消息的時候,就會調用didReceiveMessage這個代理方法,我們只需要在這個方法中再次調用reloadMessage方法就可.(PS:因為我用的是storyboard 所有有些地方會有所不同??)



方法解釋:


-(void)reloadMessage

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

 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    //這里面要填的是XMPPARChiver的coreData實例類型
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    
    //對取到的數據進行過濾,傳入過濾條件.
    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
    
    //設置排序的關鍵字
    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協議實現即時通訊的過程大體就是這樣了,當然了,我還有一些功能沒有完善,比如添加好友的功能.這將在后期進行再次的完善.謝謝大家的查看..
--->XMPP的Demo資源下載
在Demo拿到手的時候,我們首先要對我們的服務器IP進行設置.在XMPPConfig.h文件中設置服務器相關信息!!!


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

推薦閱讀更多精彩內容