XMPP即时聊天步骤

XMPP核心的几个类  :XMPPStream(通讯管道)、XMPPJID(用户标示)、XMPPPresence(出席通知)、XMPPRoster(花名册)

一.登录注册

1.创建单例(XMPPManager)

static XMPPManager *manager = nil;
+ (XMPPManager *)sharedManager{
    // GCD 创建单例对象
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[XMPPManager alloc] init];
    });
    return manager;
}

2.配置通讯管道

- (instancetype)init{
    self = [super init];
    if (self) {
        //-----------------配置通信管道---------------
        self.stream = [[XMPPStream alloc] init];
        // 设置通信管道的目标服务器地址
        _stream.hostName = kHostName;
        // 设置通信管道的xmpp server端口
        _stream.hostPort = kHostPort;
        // 设置代理
        [_stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return self;
}

3.构造XMPPJID

// 根据一个用户名构造一个xmppjid
XMPPJID *myjid = [XMPPJID jidWithUser:userName domain:kDomin resource:kResource];
// 设置通信管道的jid
_stream.myJID = myjid;

4.连接服务器

if ([_stream isConnected]) {
        NSLog(@"已经连接");
        [_stream disconnect]; // 断开连接
    }
    BOOL result = [_stream connectWithTimeout:30 error:nil];
    if (result){
        NSLog(@"服务器链接成功");
    }else{
        NSLog(@"服务器链接失败");
    }

5.连接服务器常用代理方法

// 服务器连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender{
    NSLog(@"服务器连接超时");
}
// 断开连接
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    if (error) {
        NSLog(@"%@",error);
    }
    // 离线消息(下线通知)
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
    // 发送下线通知
    [_stream sendElement:presence];
}

6.登陆 先连接服务器,再登陆

// 连接成功之后发起登陆事件
[_stream authenticateWithPassword:self.loginPassword error:nil];

7.登陆常用代理方法

// 登陆成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    NSLog(@"登陆成功");
    // 出席消息(上线通知)
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
    // 发送上线通知
    [_stream sendElement:presence];
}
// 登陆失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    NSLog(@"登陆失败:%@",error);
}

8.注册 先连接服务器,再注册

// 连接成功之后发起注册事件

            [_stream registerWithPassword:self.regPassword error:nil];

9.注册常用的代理方法

// 注册成功

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

    NSLog(@"注册成功");

}

// 注册失败

- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{

    NSLog(@"注册失败, error:%@",error);

}

二. 好友列表、添加好友、发送消息、接收消息

1.单例中创建花名册的属性 (XMPPRoster 可以处理和好友相关的事:获取好友列表,添加好友,接收好友请求,同意添加好友,拒绝添加好友)

/**
 *  好友花名册,用来处理和好友相关的事件
 */
@property(nonatomic, strong) XMPPRoster * roster;

2.初始化一个花名册,并且在通讯管道中激活花名册

//------------------用户花名册----------------

        // xmpp为我们提供了一个CoreData存储器

        XMPPRosterCoreDataStorage *xrcds = [XMPPRosterCoreDataStorage sharedInstance];

        // 创建roster 花名册时,需要给花名册指定一个数据存储的地方(就是XMPPRosterCoreDataStorage)

        self.roster = [[XMPPRoster alloc] initWithRosterStorage:xrcds dispatchQueue:dispatch_get_main_queue()];

        // 在通讯管道中激活花名册

        // 这时就可以通过通讯管道去给服务器发送请求了。

        // 然后roster的消息都通过stream间接的发给服务器

        [self.roster activate:self.stream];

3.设置花名册代理,在好友列表控制器里面设置,列表控制器遵循代理 XMPPRosterDelegate// 设置代理

    [[XMPPManager sharedManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

4.声明一个数组存放好友

@property (nonatomic, strong) NSMutableArray *rosters;

5.花名册常用代理方法

// 开始接收好友列表
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
    NSLog(@"开始接收好友列表");
}

// 接收完毕
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
    NSLog(@"结束接收好友列表");
}

// 每次接收到一个好友就会走一次这个方法
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item{

    NSString *jid = [[item attributeForName:@"jid"] stringValue];

    XMPPJID *xmppjid = [XMPPJID jidWithString:jid resource:kResource];

    [self.rosters addObject:xmppjid];

    NSIndexPath *indexpath = nil;

    if (self.rosters.count == 0) return;

    indexpath = [NSIndexPath indexPathForRow:self.rosters.count - 1 inSection:0];

    [self.tableView insertRowsAtIndexPaths:@[indexpath] withRowAnimation:(UITableViewRowAnimationLeft)];

}

// 收到添加好友请求 :(同意:[roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];)

//(拒绝:[roster rejectPresenceSubscriptionRequestFrom:presence.from];)

- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence{

    XMPPRoster *roster = [XMPPManager sharedManager].roster;

    NSString *message = [NSString stringWithFormat:@"%@请求加你为好友", presence.from.user];

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"好友请求" message:message preferredStyle:(UIAlertControllerStyleAlert)];

    UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"同意" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
        // 同意请求
        [roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];
    }];

    UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"拒绝" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
        // 拒绝请求
        [roster rejectPresenceSubscriptionRequestFrom:presence.from];
    }];

    [alert addAction:action1];
    [alert addAction:action2];
    [self presentViewController:alert animated:YES completion:nil];
}

6.添加好友(添加好友界面)

- (IBAction)actionAdd:(UIButton *)sender {
    // 拿到花名册管理类
    XMPPRoster *roster = [XMPPManager sharedManager].roster;
    // 拼装要添加的好友
    XMPPJID *userjid = [XMPPJID jidWithUser:self.txtUserName.text domain:kDomin resource:kResource];
    // 添加好友请求
    [roster subscribePresenceToUser:userjid];
}

7.建立聊天

7.1 声明属性 (声明一个XMPPJID类型的属性,记录要聊天的对象)

// 当前聊天对象
@property(nonatomic, strong) XMPPJID * jidChatTo;

7.2 选择聊天对象(可视化编程中)

点击好友列表页面中的一个好友,将这个好友的XMPPJID传给聊天页面。
 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
     if ([sender isKindOfClass:[UITableViewCell class]]) {
         NSIndexPath *index = [self.tableView indexPathForCell:(UITableViewCell *)sender];
         XMPPJID *jid = self.rosters[index.row];
         // 属性传值
         ChatViewController *chatVC = [segue destinationViewController]; // 获取目标控制器
         chatVC.jidChatTo = jid;
     }

7.3 声明聊天界面属性

声明聊天相关的控件属性以及其他属性
@property (weak, nonatomic) IBOutlet UITableView *tableChat; // 展示聊天记录

@property (weak, nonatomic) IBOutlet UITextField *txtMessage; // 输入消息内容

@property (weak, nonatomic) IBOutlet UIView *viewInput; // 底部的输入控件

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *constraintBottomFromSuper; // 底部frame约束

@property (nonatomic, strong) NSMutableArray *messages; // 存放聊天信息

7.4 获取聊天记录

单例(XMPPManager)里面声明CoreData的上下文对象属性,以及聊天消息的归档处理类的对象属性
/**
 *  消息归档处理类
 *  程序关闭后,下次打开还可以再次查看以前的聊天记录
 */
@property(nonatomic, strong) XMPPMessageArchiving * messageArchiving;

/**
 *  coredata 上下文,用来获取通过messageArchiving归档后存储起来的消息
 */
@property(nonatomic, strong) NSManagedObjectContext * context;

单例对象初始化(init方法)里面对上下文,和归档处理对象的一些操作

//------------------初始化 XMPPMessageArchiving------
        // xmppMessageArchiving的主要功能:1、通过通讯管道获取到服务器发送过来的消息。2、将消息存储到指定的XMPPMessageArchivingCoreStorage
        // xmpp为我们提供的一个存储聊天消息的coredata仓库
        XMPPMessageArchivingCoreDataStorage *xmacds = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        // 初始化时,需要给这个归档类指定一个存储仓库
        self.messageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:xmacds dispatchQueue:dispatch_get_main_queue()];
        // 在通讯管道中激活
        [self.messageArchiving activate:self.stream];
        // 获取消息归档类提供的上下文信息
        self.context = xmacds.mainThreadManagedObjectContext;
聊天页面里面通过单例中获得的上下文对象,从CoreData中取到聊天记录数据
// 加载所有信息(通过单例类的上下文获取)
- (void)reloadAllMessage{
    // 获取上下文信息
    NSManagedObjectContext *context = [XMPPManager sharedManager].context;
    // xmppMessageArchving : 把接收到得消息归档,归档后的数据类型是:XMPPMessageArchiving_Message_CoreDataObject
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];
    // 设置断言
    // 查找所有的和当前聊天对象的聊天记录
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr = %@",self.jidChatTo.bare];
    // 让断言生效
    [fetchRequest setPredicate:predicate];
    // 获取数据
    NSArray *array = [context executeFetchRequest:fetchRequest error:nil];
    if (array) {
        // 将原来的数据清空
        [self.messages removeAllObjects];
    }
    // 把获取的数据添加到当前数据源中
    [self.messages addObjectsFromArray:array];
    // 刷新列表
    [self.tableChat reloadData];
    // 将视图定位到最新的一条消息。
    if (array.count > 0) {
        NSIndexPath *index = [NSIndexPath indexPathForItem:array.count - 1 inSection:0];
        [self.tableChat scrollToRowAtIndexPath:index atScrollPosition:(UITableViewScrollPositionBottom) animated:YES];
    }
}
展示聊天内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    // 获取对应的消息
    XMPPMessageArchiving_Message_CoreDataObject *message = self.messages[indexPath.row];
    // 判断是不是自己发出去的
    if ([message isOutgoing]) {
        cell.textLabel.text = [NSString stringWithFormat:@"我:%@",message.body];
    }else{
        cell.textLabel.text = [NSString stringWithFormat:@"%@:%@",message.bareJidStr, message.body];
    }
    return cell;
}

7.5 发送和接收消息

设置通讯管道代理
XMPPStream *stream = [XMPPManager sharedManager].stream;
[stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
通讯管道代理中和发送以及接收消息相关的代理方法
// 收到信息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
    NSLog(@"接收到一条消息:%@",message);
    [self reloadAllMessage]; // 加载一遍所有的聊天数据
}
// 发送信息
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
    NSLog(@"消息【%@】发送成功",message);
    [self reloadAllMessage]; // 加载一遍所有聊天数据
}
发送消息
- (IBAction)actionSendMsg:(UIButton *)sender {
    XMPPStream *stream = [XMPPManager sharedManager].stream;
    // 实例化一个消息类
    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.jidChatTo];
    // 设置消息内容
    [message addBody:self.txtMessage.text];
    // 通过通讯管道发送
    [stream sendElement:message];
}

其他:处理键盘弹出相关:

注册键盘frame改变的通知:
// 通过通知中心来观察键盘的frame的变化,当键盘frame发生变化后触发keyboardFrameChange事件
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChange:) name:UIKeyboardDidChangeFrameNotification object:nil];
处理方法及键盘回收代理事件:
// 键盘frame改变后触发事件
- (void)keyboardFrameChange:(NSNotification *)sender{
    // 键盘改变后的frame
    CGRect rect = [[sender.userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
    // 计算出聊天窗口的底部偏移量
    CGFloat height = self.view.frame.size.height - rect.origin.y;
    self.constraintBottomFromSuper.constant = height;
}
#pragma mark -UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
    [textField resignFirstResponder];
    return YES;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容