iOS 即时通信系列之XMPP登入、注册、聊天

说明:在即时通信系列XMPP之搭建本地服务器中阐述了通过XAMPP、Openfire工具搭建基于XMPP的服务器端,并使用spark软件进行XMPP客户端的调试。在本文中主要阐述将XMPP添加到工程中,实现iOS中基于XMPP的即时通信。

项目环境配置

使用CocoaPods导入XMPP框架,如果不会CocoaPods,点击CocoaPods安装及使用教程,或者手动导入XMPP框架

创建XMPP管理类

为了让XMPP不会对工程中其它业务代码具有侵入性,方便我们管理整个关于XMPP的逻辑处理,通常会采用单例模式新建一个专门的管理类,为此,在工程中创建一个继承自NSObject的XMPPManger,为了能够在代码中方便调用XMPPStreamDelegate的方法,让其遵守XMPPStreamDelegate协议。
  XMPP框架中所有的连接、登入、注册、授权、失去连接等操作的回调都是通过XMPPStream来管理,所以XMPPManger必须有一个XMPPStream实例。其它的还包括进行添加好友、删除好友、获取好友列表等功能的XMPPRoster;消息保存组件XMPPMessageArchiving等。

XMPPManager.h
@interface XMPPManager : NSObject<XMPPStreamDelegate>

/**
 *  创建全局唯一的管理者对象
 *  @return 返回一个单例对象
 */
+ (XMPPManager *)sharedXMPPManager;

/**
 *  信息管道
 */
@property (nonatomic,strong)XMPPStream *stream;
/**
 *  进行添加好友 删除好友 获取好友列表等功能
 */
@property (nonatomic,strong)XMPPRoster *roster;
/**
 *  消息保存组件
 */
@property (nonatomic,strong)XMPPMessageArchiving *messageArchiving;
/**
 *  消息保存组件的CoreData上下文
 */
@property (nonatomic,strong)NSManagedObjectContext *managerObjectContext;
/**
 *  登入
 *
 *  @param userName 账户
 *  @param password 密码
 */
- (void)loginWithUserName:(NSString *)userName password:(NSString *)password;
/**
 *  注册
 *
 *  @param userName 账户
 *  @param password 密码
 */
- (void)registWithUserName:(NSString *)userName password:(NSString *)password;

@end
XMPPManager.m
//服务器的地址
static NSString *const kHostName = @"127.0.0.1";
//端口号
static UInt16 const kHostPort = 5222;

//连接服务器类型
typedef NS_ENUM(NSUInteger,ConnectToServerStatus) {
    ConnectToServerStatusLogin,
    ConnectToServerStatusRegist,
};

@interface XMPPManager ()
//登入密码
@property (nonatomic,strong)NSString *loginpassword;
//注册密码
@property (nonatomic,strong)NSString *registpassword;
//是登入还是注册
@property (nonatomic,assign)ConnectToServerStatus connectToSercerStatus;

@end

@implementation XMPPManager

+ (XMPPManager *)sharedXMPPManager{
    static XMPPManager *xmppManager = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        xmppManager = [[XMPPManager alloc] init];
    });
    return xmppManager;
}

- (instancetype)init{
    if ([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()];
        
        //进行好友存储
        XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
        self.roster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_main_queue()];
        //激活
        [self.roster activate:self.stream];
        
        //进行聊天信息存储
        XMPPMessageArchivingCoreDataStorage *messageArchivingStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        self.messageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:messageArchivingStorage dispatchQueue:dispatch_get_main_queue()];
        [self.messageArchiving activate:self.stream];
        
        self.managerObjectContext = messageArchivingStorage.mainThreadManagedObjectContext;
    }
    return self;
}

//与服务器建立链接
-(void)connectToServerWithUser:(NSString *)user{
    //要是正在链接的话那么就先断开连接
    if ([self.stream isConnected]) {
        [self disconnectServer];
    }
    XMPPJID *jid = [XMPPJID jidWithUser:user domain:@"DH_Fantasy" resource:@"iPhone"];
    self.stream.myJID = jid;
    NSError *error = nil;
    [self.stream connectWithTimeout:30.0f error:&error];
    if (nil != error) {
        NSLog(@"%s__%d__链接出错:%@",__FUNCTION__,__LINE__,error);
    }
}

//与服务器断开链接
-(void)disconnectServer{
    [self.stream disconnect];
}
//登入
- (void)loginWithUserName:(NSString *)userName password:(NSString *)password{
    self.connectToSercerStatus = ConnectToServerStatusLogin;
    self.loginpassword = password;//将传进来的password传给self.password
    [self connectToServerWithUser:userName];
}
//注册
- (void)registWithUserName:(NSString *)userName password:(NSString *)password{
    self.connectToSercerStatus = ConnectToServerStatusRegist;
    self.registpassword = password;
    [self connectToServerWithUser:userName];
}

#pragma mark XMPPStreamDelegate
//与服务器链接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
#pragma mark 判断与服务器建立连接是登陆还是注册
    switch (self.connectToSercerStatus) {
        case ConnectToServerStatusLogin:
        {
            NSError *error = nil;
            [self.stream authenticateWithPassword:self.loginpassword error:&error];
            if (nil != error) {
                NSLog(@"%s__%d__验证出错:%@",__FUNCTION__,__LINE__,error);
            }
            break;
        }
        case ConnectToServerStatusRegist:
        {
            NSError *err = nil;
            [self.stream registerWithPassword:self.registpassword error:&err];
            if (nil != err) {
                NSLog(@"%s__%d__注册出错:%@",__FUNCTION__,__LINE__,err);
            }
            break;
        }
        default:
            break;
    }
}
//与服务器链接失败
-(void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
    NSLog(@"😂与服务器链接失败");
}

- (void)xmppStreamWillConnect:(XMPPStream *)sender {
    NSLog(@"🔌socket正在连接...");
}

- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket {
    NSLog(@"🍎socket连接成功");
    // 连接成功之后,由客户端xmpp发送一个stream包给服务器,服务器监听来自客户端的stream包,并返回stream feature包
}

- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error {
    NSLog(@"😂xmpp授权失败:%@", error.description);
}

- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
    NSLog(@"🍎xmpp授权成功。");
    // 只有进入到这里,才算是真正的可以聊天了
}

- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error {
    NSLog(@"😂xmpp失去连接。");
}

@end

AppDelegate

在AppDelegate中主要设置了下进入APP时的登入状态,在一些场景中,某些控制器必须为登入状态才能进入,用NSUserDefaults存储为Bool类型,这为判断是否登入提供了方便。

AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //设置主窗口并显示
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    //设置进入APP为没登入状态
    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isLoginStatus"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    //设置好友列表控制器为根视图
    RosterTableViewController *VC = [[RosterTableViewController alloc] init];
    UINavigationController *NC = [[UINavigationController alloc] initWithRootViewController:VC];
    self.window.rootViewController = NC;
    
    return YES;
}

RosterTableViewController

RosterTableViewController主要作用就是进行自动登入与显示获取到的好友。

RosterTableViewController.m
@interface RosterTableViewController ()

//用于保存获取到的好友
@property (nonatomic,strong)NSMutableArray *rosterArray;

@end

@implementation RosterTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = @"好友列表";
    
    self.rosterArray = [[NSMutableArray alloc] init];
    
    //判断之前是否登入过
    if (nil != [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"]) {
        
        //之前登入过,直接读取用户名和密码进行连接
        NSString *userName = [[NSUserDefaults standardUserDefaults]objectForKey:@"userName"];
        NSString *password = [[NSUserDefaults standardUserDefaults]objectForKey:@"password"];
        [[XMPPManager sharedXMPPManager]loginWithUserName:userName password:password];
        [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
    }else{
        
        //之前没登入过则进入到登入窗口
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        LoginViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:@"login"];
        
        [self.navigationController pushViewController:loginVC animated:YES];
    }
    
    [[XMPPManager sharedXMPPManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

#pragma mark UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:@"cell"];
    }
    
    XMPPJID *jid = self.rosterArray[indexPath.row];
    cell.textLabel.text = jid.user;
    
    return cell;
}

#pragma mark 进入聊天界面
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    XMPPJID *jid = self.rosterArray[indexPath.row];
    ChatViewController *chat = [[ChatViewController alloc] init];
    chat.chatToJid = jid;
    [self.navigationController pushViewController:chat animated:YES];
}

#pragma mark XMPPStreamDelegate
//授权成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isLoginStatus"];
    
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    //我们验证之后默认的状态是离线的 于是我们想显示在线,需要告诉服务器自己的状态
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
    [[XMPPManager sharedXMPPManager].stream sendElement:presence];
}

//验证失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    NSLog(@"登入验证失败😂");
}

#pragma mark XMPPRosterDelegate
//刚开始获取好友列表
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
    NSLog(@"开始获取好友列表");
}

//正在获取好友列表
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{
    NSLog(@"正在获取好友列表...%@",item);
    NSString *jidStr = [[item attributeForName:@"jid"]stringValue];
    XMPPJID *jid =[XMPPJID jidWithString:jidStr];
    [self.rosterArray addObject:jid];
    
    //将数据添加进数组
    [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.rosterArray.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
}

//已经完成好友列表获取
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
    NSLog(@"已经完成好友列表获取🍅");
}

Friends List.png

LoginViewController

LoginViewController中使用XMPPManager单例在登入按钮的点击事件中调用- (void)loginWithUserName:(NSString ***)userName password:(NSString *)password;方法进行登入。

LoginViewController.m
@interface LoginViewController ()

//登入账户
@property (weak, nonatomic) IBOutlet UITextField *userName;
//登入密码
@property (weak, nonatomic) IBOutlet UITextField *password;

@end

@implementation LoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

//登入操作
- (IBAction)loginAction:(id)sender {
    [[XMPPManager sharedXMPPManager] loginWithUserName:self.userName.text password:self.password.text];
}

#pragma mark XMPPStreamDelegte
//验证成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    //存储账户密码用于下次自动登入
    [[NSUserDefaults standardUserDefaults] setObject:self.userName.text forKey:@"userName"];
    [[NSUserDefaults standardUserDefaults] setObject:self.password.text forKey:@"password"];
    
    //改变APP的登入状态
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isLoginStatus"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    //验证之后默认的状态为离线,需要手动告诉服务器自己的状态
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
    [[XMPPManager sharedXMPPManager].stream sendElement:presence];
    
    //返回到根视图
    [self.navigationController popToRootViewControllerAnimated:YES];
}

//验证失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    NSLog(@"登入验证失败😂");
}
LoginViewController.png

RegisterViewController

注册和登入比较相似,只需在注册按钮的点击事件中使用XMPPManager单例调用- (void)registWithUserName:(NSString *)userName password:(NSString *)password;方法进行注册。

RegisterViewController.m
@interface RegisterViewController ()

//注册账户
@property (weak, nonatomic) IBOutlet UITextField *userName;
//注册密码
@property (weak, nonatomic) IBOutlet UITextField *password;
//密码验证
@property (weak, nonatomic) IBOutlet UITextField *rePassword;

@end

@implementation RegisterViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

- (IBAction)registerAction:(id)sender {
    //简单验证:用户名不为空且密码和密码验证输入一致
    if (![self.userName.text isEqualToString:@""] && [self.rePassword.text isEqualToString:self.password.text]) {
        [[XMPPManager sharedXMPPManager] registWithUserName:self.userName.text password:self.password.text];
    }else{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"注册失败" message:@"" preferredStyle:(UIAlertControllerStyleAlert)];
        
        if ([self.userName.text isEqualToString:@""]) {
            alertController.title = @"账户为空";
            alertController.message = @"请输入账户";
        }
        if (![self.rePassword.text isEqualToString:self.password.text]) {
            alertController.title = @"密码验证错误";
            alertController.message = @"请重新输入密码验证";
        }
        
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
            [alertController dismissViewControllerAnimated:YES completion:nil];
        }];
        
        [alertController addAction:okAction];
        [self presentViewController:alertController animated:YES completion:nil];
    }
}

#pragma mark--XMPPStreamDelegate
//注册成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender{
    NSLog(@"%s__%d__注册成功",__FUNCTION__,__LINE__);
    
    //存储账户密码用于下次自动登入
    [[NSUserDefaults standardUserDefaults] setObject:self.userName.text forKey:@"userName"];
    [[NSUserDefaults standardUserDefaults] setObject:self.password.text forKey:@"password"];
    
    //改变APP的登入状态
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isLoginStatus"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    //验证之后默认的状态为离线,需要手动告诉服务器自己的状态
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
    [[XMPPManager sharedXMPPManager].stream sendElement:presence];
    
    //注册成功之后返回到登陆界面
    [self.navigationController popToRootViewControllerAnimated:YES];
}
//注册失败
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
    NSLog(@"%s__%d__注册失败%@",__FUNCTION__,__LINE__,error);
}

RegisterViewController.png

ChatViewController

在ChatViewController中需要公开一个属性,用来接收好友列表页面传来的XMPPJID。

ChatViewController.h
@interface ChatViewController : UIViewController

@property (nonatomic,strong)XMPPJID *chatToJid;

@end
ChatViewController.m
@interface ChatViewController ()<UITableViewDataSource,UITableViewDelegate>

//显示消息
@property (weak, nonatomic) IBOutlet UITableView *messageContent;
//消息输入框
@property (weak, nonatomic) IBOutlet UITextView *messageTextView;
//键盘底部约束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageTextfieldConstraint;
//存储消息数据
@property (nonatomic,strong) NSMutableArray *allMessageArray;

@end

@implementation ChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.messageTextView.text = @"";

    self.messageContent.delegate = self;
    self.messageContent.dataSource = self;
    
    self.allMessageArray = [[NSMutableArray alloc] init];
    //将历史消息加入到数组
    [self.allMessageArray addObjectsFromArray:[self fromDataBaseFetchResult]];
    
    //监听键盘frame的改变
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(kbFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
    
    [[XMPPManager sharedXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    
    [self reloadMessage];
    
    self.messageContent.separatorStyle = UITableViewCellSeparatorStyleNone;
}

//消息发送按钮
- (IBAction)sendMessageAction:(id)sender {
    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJid];
    //要发送的消息添加到Body
    [message addBody:self.messageTextView.text];
    //发送消息
    [[XMPPManager sharedXMPPManager].stream sendElement:message];
}

//键盘即将改变frame
- (void)kbFrameWillChange:(NSNotification *)noti{
    //获取窗口的高度
    CGFloat windowH = [UIScreen mainScreen].bounds.size.height;
    //键盘结束的frm
    CGRect kbEndFrm = [noti.userInfo [UIKeyboardFrameEndUserInfoKey]CGRectValue];
    //键盘结束的y值
    CGFloat kbEndY = kbEndFrm.origin.y;
    self.messageTextfieldConstraint.constant = windowH - kbEndY;
    
    [self scrollsToBottomAnimated:YES];
}

- (void)scrollsToBottomAnimated:(BOOL)animated{
    CGFloat offset = self.messageContent.contentSize.height - self.messageTextfieldConstraint.constant - 55;
    if (offset > 0){
        [self.messageContent setContentOffset:CGPointMake(0, offset) animated:animated];
    }
}

#pragma mark XMPPStreamDelegate
//发送信息成功
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
    NSLog(@"发送成功🍅");
    [self reloadMessage];//发送信息时刷新一次页面
}

//发送信息失败
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error{
    NSLog(@"发送失败⚠️");
}

//收到信息
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
    NSLog(@"收到消息%@",message);
    if ([message isChatMessageWithBody]) {
        [self reloadMessage];//收到信息是刷新一次页面
    }
}

#pragma mark 加载聊天信息
-(void)reloadMessage{
    
    //从数据库读取数据
    NSArray *fetchedObjects = [self fromDataBaseFetchResult];
    
    // 清空消息数组里的所有数据
    [self.allMessageArray removeAllObjects];
    // 将新的聊天记录添加到数组中
    [self.allMessageArray addObjectsFromArray:fetchedObjects];
    
    //将信息加载到tableView上
    [self.messageContent reloadData];
    
    //加载时滚动到最底部
    [self scrollToBottomWithAnimated:YES];
}

//从数据库读取数据
- (NSArray *)fromDataBaseFetchResult{
    //1.上下文
    NSManagedObjectContext *managerObjectContext =[XMPPManager sharedXMPPManager].managerObjectContext;
    
    //2.创建查询请求
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];
    
    //3.设置过滤条件(提取当前用户jid,好友jid)
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ And streamBareJidStr == %@", self.chatToJid.bare,[XMPPManager sharedXMPPManager].stream.myJID.bare];
    fetchRequest.predicate = predicate;
    
    //4.设置排序(时间升序)
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES];
    fetchRequest.sortDescriptors = @[sortDescriptor];
    
    //5.执行请求
    NSError *error = nil;
    NSArray *fetchedResult = [managerObjectContext executeFetchRequest:fetchRequest error:&error];
    
    return fetchedResult;
}

//滚动到最底部
- (void)scrollToBottomWithAnimated:(BOOL)animated{
    
    if (animated && [self.messageContent numberOfSections] > 0) {
        
        NSInteger lastSectionIndex = [self.messageContent numberOfSections] - 1;
        NSInteger lastRowIndex = [self.messageContent numberOfRowsInSection:lastSectionIndex] - 1;
        
        if (lastRowIndex > 0) {
            
            NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
            [self.messageContent scrollToRowAtIndexPath:lastIndexPath atScrollPosition: UITableViewScrollPositionBottom animated:animated];
        }
    }
}

#pragma mark UITableViewDataSourceDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.allMessageArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (nil == cell) {
        cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleValue1) reuseIdentifier:@"cell"];
    }
    
    XMPPMessageArchiving_Message_CoreDataObject *chatMessage = self.allMessageArray[indexPath.row];
    
    //判断聊天信息是发出去的 还是接受进来的,在cell上显示不一样的样式
    if (chatMessage.isOutgoing == YES) {
        cell.detailTextLabel.text = chatMessage.body;
        cell.textLabel.text = @"";
    } else {
        cell.textLabel.text = chatMessage.body;
        cell.detailTextLabel.text = @"";
    }
    
    self.messageTextView.text = @"";
    
    return cell;
}
Use Test_XMPP And Spark Chat.png

以上就是将XMPP添加到工程,实现基于XMPP的登入、注册、聊天功能,还有一大批别的功能有待探索,之后将进行持续的更新。

总结

1.XMPP框架中所有的连接、登入、注册、授权、失去连接等操作的回调都是通过XMPPStream来管理;
2.使用XMPP开发即时通信,大部分功能都是通过回调XMPPStreamDelegate协议中的方法来实现。


联系作者:简书·DH_Fantasy 新浪微博·DH_Fantasy
版权声明:自由转载-非商用-非衍生-保持署名(CC BY-NC-ND 3.0

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容