消息

消息:IM 交互实体,在 SDK 中对应的类型是 EMMessage,EMMessage 可以由多个符合 <IEMMessageBody> 协议的 body 组成,但是,推荐使用一个 body,多个 body 有 bug,正在优化。

以下的讲解以一个 body 为例:

构造文字消息

EMChatText *txtChat = [[EMChatText alloc] initWithText:@"要发送的消息"];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:txtChat];

// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造图片消息

EMChatImage *imgChat = [[EMChatImage alloc] initWithUIImage:img displayName:@"displayName"];
EMImageMessageBody *body = [[EMImageMessageBody alloc] initWithChatObject:imgChat];

// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造位置消息

EMChatLocation *locChat = [[EMChatLocation alloc] initWithLatitude:35.1 longitude:35.1 address:@"地址"];
EMLocationMessageBody *body = [[EMLocationMessageBody alloc] initWithChatObject:locChat];

// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造语音消息

EMChatVoice *voice = [[EMChatVoice alloc] initWithFile:recordPath displayName:@"audio"];
voice.duration = aDuration;
EMVoiceMessageBody *body = [[EMVoiceMessageBody alloc] initWithChatObject:voice];

// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造视频消息

EMChatVideo *videoChat = [[EMChatVideo alloc] initWithFile:localPath displayName:@"displayName"];
EMVideoMessageBody *body = [[EMVideoMessageBody alloc] initWithChatObject:videoChat];
// 生成message。 6001,环信id,消息接收方。
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造文件消息

EMChatFile *fileChat = [[EMChatFile alloc] initWithFile:localPath displayName:@"displayName"];
EMFileMessageBody *body = [[EMFileMessageBody alloc] initWithChatObject:fileChat];
// 生成message。 6001,环信id,消息接收方。
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造透传消息

SDK 提供的一种特殊类型的消息,即 CMD,不会存 db,也不会走 APNS 推送,类似一种指令型的消息,比如您的服务器要通知客户端做某些操作,您可以服务器和客户端提前约定好某个字段,当客户端收到约定好的字段时,执行某种特殊操作。

EMChatCommand *cmdChat = [[EMChatCommand alloc] init];
cmdChat.cmd = @"reason";
EMCommandMessageBody *body = [[EMCommandMessageBody alloc] initWithChatObject:cmdChat];
// 生成message。 6001,环信id,消息接收方。
EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
message.messageType = eMessageTypeChat; // 设置为单聊消息
//message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
//message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息

构造扩展消息

有时候需要在消息中携带一些扩展内容,用来实现特殊需求,比如阅后即焚等。EMMessage 提供了 ext 属性,撰文用来存放扩展内容。ext 属性是 NSDictionary 类型,key 和 value 必须是基本类型,且不能是 JSON。

可以这样使用

EMChatText *txt = [[EMChatText alloc] initWithText:@"test1"];
    EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:txt];
    // 6001,环信id,消息接收方。
    EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]]; 
    message.ext = @{@"key":@"value"};

ext 是message的属性,所有消息类型的message都有该属性

插入消息

EMChatText *txt = [[EMChatText alloc] initWithText:@"test1"];
    EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:txt];
    // 6001,环信id,消息接收方。
    EMMessage *message = [[EMMessage alloc] initWithReceiver:@"6001" bodies:@[body]];
    message.messageType = eMessageTypeChat; // 设置为单聊消息
    //message.messageType = eConversationTypeGroupChat;// 设置为群聊消息
    //message.messageType = eConversationTypeChatRoom;// 设置为聊天室消息
    message.deliveryState = eMessageDeliveryState_Delivered;
    [[EaseMob sharedInstance].chatManager insertMessageToDB:message];

更新消息属性

/*!
 @method
 @brief  更新消息发送状态
 @result 是否更新成功
 */
- (BOOL)updateMessageDeliveryStateToDB;

/*!
 @method
 @brief  更新消息扩展属性
 @result 是否更新成功
 */
- (BOOL)updateMessageExtToDB;

/*!
 @method
 @brief  更新消息的消息体
 @result 是否更新成功
 */
- (BOOL)updateMessageBodiesToDB;

/*!
 @method
 @brief  修改当前 message 的发送状态,下载状态为 failed(crash 时或者 terminate)
 @return 是否更新成功
 */
- (BOOL)updateMessageStatusFailedToDB;

会话:操作聊天消息 EMMessage 的容器,在 SDK 中对应的类型是 EMConversation

新建/获取一个会话

根据 chatter 创建一个 conversation。

EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:@"8001" conversationType:eConversationTypeChat];
  • conversationForChatter: 获取或创建与8001的会话
  • conversationType: 会话类型

删除会话

删除单个会话

[[EaseMob sharedInstance].chatManager removeConversationByChatter:@"8001" deleteMessages:YES append2Chat:YES];
  • removeConversationByChatter: 删除与8001的会话
  • deleteMessages: 删除会话中的消息
  • append2Chat: 是否更新内存中内容

批量删除会话

根据 chatter 批量删除会话。

[[EaseMob sharedInstance].chatManager removeConversationsByChatters:chatters deleteMessages:YES append2Chat:YES];
  • removeConversationsByChatters: 要删除的 chatters
  • deleteMessages: 删除会话中的消息
  • append2Chat: 是否更新内存中内容

删除所有会话

// deleteMessage,是否删除会话中的message,YES为删除
[[EaseMob sharedInstance].chatManager removeAllConversationsWithDeleteMessages:YES append2Chat:YES];

获取会话列表

SDK 中提供了三种获取会会话列表的方法。

获取或创建

EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:@"8001" conversationType:eConversationTypeChat];
  • conversationForChatter: 获取或创建与8001的会话
  • isGroup: 会话类型

获取内存中所有会话

NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];

获取 DB 中的所有会话

NSArray *conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES];

获取会话未读消息数

[conversation unreadMessagesCount];

登录成功之后才能进行聊天操作。发消息时,单聊和群聊调用的是统一接口,区别只是要设置下 message.isGroup 属性。坚决不推荐多 body。

发送消息

/*!
@method
@brief 发送一条消息
@discussion 待发送的消息对象和发送后的消息对象是同一个对象,在发送过程中对象属性可能会被更改
@param message  消息对象(包括from、to、body列表等信息)
@param progress 发送多媒体信息时的progress回调对象
@param pError   错误信息
@result 发送完成后的消息对象
*/
- (EMMessage *)sendMessage:(EMMessage *)message
                  progress:(id<IEMChatProgressDelegate>)progress
                     error:(EMError **)pError;

/*!
@method
@brief 异步方法,发送一条消息
@discussion 待发送的消息对象和发送后的消息对象是同一个对象,在发送过程中对象属性可能会被更改。在发送过程中,willSendMessage:error:和didSendMessage:error:这两个回调会被触发
@param message  消息对象(包括from、to、body列表等信息)
@param progress 发送多媒体信息时的progress回调对象
@result 发送的消息对象(因为是异步方法,不能作为发送完成或发送成功失败与否的判断)
*/
- (EMMessage *)asyncSendMessage:(EMMessage *)message
                       progress:(id<IEMChatProgressDelegate>)progress;

/*!
@method
@brief 异步方法,发送一条消息
@discussion 待发送的消息对象和发送后的消息对象是同一个对象,在发送过程中对象属性可能会被更改
@param message  消息对象(包括from、to、body列表等信息)
@param progress 发送多媒体信息时的progress回调对象
@param prepare          将要发送消息前的回调block
@param aPrepareQueue    回调block时的线程
@param completion       发送消息完成后的回调
@param aCompletionQueue 回调block时的线程
@result 发送的消息对象(因为是异步方法,不能作为发送完成或发送成功失败与否的判断)
*/
- (EMMessage *)asyncSendMessage:(EMMessage *)message
                       progress:(id<IEMChatProgressDelegate>)progress
                        prepare:(void (^)(EMMessage *message, EMError *error))prepare
                        onQueue:(dispatch_queue_t)aPrepareQueue
                     completion:(void (^)(EMMessage *message, EMError *error))completion
                        onQueue:(dispatch_queue_t)aCompletionQueue;

接收离线消息

离线普通消息会走以下回调:

/*!
@method
@brief 将要接收离线消息的回调
@discussion
@result
*/
- (void)willReceiveOfflineMessages;

/*!
 @method
 @brief 接收到离线非透传消息的回调
 @discussion
 @param offlineMessages 接收到的离线列表
 @result
 */
- (void)didReceiveOfflineMessages:(NSArray *)offlineMessages;

/*!
 @method
 @brief 离线非透传消息接收完成的回调
 @discussion
 @param offlineMessages 接收到的离线列表
 @result
 */
- (void)didFinishedReceiveOfflineMessages;

离线透传消息会走以下回调:

/*!
 @method
 @brief 接收到离线透传消息的回调
 @discussion
 @param offlineCmdMessages 接收到的离线透传消息列表
 @result
 */
- (void)didReceiveOfflineCmdMessages:(NSArray *)offlineCmdMessages;

/*!
 @method
 @brief 离线透传消息接收完成的回调
 @discussion
 @param offlineCmdMessages 接收到的离线透传消息列表
 @result
 */
- (void)didFinishedReceiveOfflineCmdMessages;

接收在线消息

在线普通消息会走以下回调:

/*!
@method
@brief 收到消息时的回调
@param message      消息对象
@discussion 当EMConversation对象的enableReceiveMessage属性为YES时,会触发此回调
针对有附件的消息,此时附件还未被下载。
附件下载过程中的进度回调请参考didFetchingMessageAttachments:progress:,
下载完所有附件后,回调didMessageAttachmentsStatusChanged:error:会被触发
*/
- (void)didReceiveMessage:(EMMessage *)message;

透传(cmd)在线消息会走以下回调:

/*!
@method
@brief 收到消息时的回调
@param cmdMessage      消息对象
@discussion 当EMConversation对象的enableReceiveMessage属性为YES时,会触发此回调
*/
- (void)didReceiveCmdMessage:(EMMessage *)cmdMessage;

消息未读数变化

/*!
@method
@brief 未读消息数改变时的回调
@discussion 当EMConversation对象的enableUnreadMessagesCountEvent为YES时,会触发此回调
@result
*/
- (void)didUnreadMessagesCountChanged;

解析普通消息

// 收到消息的回调,带有附件类型的消息可以用 SDK 提供的下载附件方法下载(后面会讲到)
-(void)didReceiveMessage:(EMMessage *)message
{
    id<IEMMessageBody> msgBody = message.messageBodies.firstObject;
    switch (msgBody.messageBodyType) {
        case eMessageBodyType_Text:
        {
            // 收到的文字消息
            NSString *txt = ((EMTextMessageBody *)msgBody).text;
            NSLog(@"收到的文字是 txt -- %@",txt);
        }
        break;
        case eMessageBodyType_Image:
        {
            // 得到一个图片消息body
            EMImageMessageBody *body = ((EMImageMessageBody *)msgBody);
            NSLog(@"大图remote路径 -- %@"   ,body.remotePath);
            NSLog(@"大图local路径 -- %@"    ,body.localPath); // // 需要使用SDK提供的下载方法后才会存在
            NSLog(@"大图的secret -- %@"    ,body.secretKey);
            NSLog(@"大图的W -- %f ,大图的H -- %f",body.size.width,body.size.height);
            NSLog(@"大图的下载状态 -- %lu",body.attachmentDownloadStatus);


            // 缩略图sdk会自动下载
            NSLog(@"小图remote路径 -- %@"   ,body.thumbnailRemotePath);
            NSLog(@"小图local路径 -- %@"    ,body.thumbnailLocalPath);
            NSLog(@"小图的secret -- %@"    ,body.thumbnailSecretKey);
            NSLog(@"小图的W -- %f ,大图的H -- %f",body.thumbnailSize.width,body.thumbnailSize.height);
            NSLog(@"小图的下载状态 -- %lu",body.thumbnailDownloadStatus);
        }
        break;
        case eMessageBodyType_Location:
        {
            EMLocationMessageBody *body = (EMLocationMessageBody *)msgBody;
            NSLog(@"纬度-- %f",body.latitude);
            NSLog(@"经度-- %f",body.longitude);
            NSLog(@"地址-- %@",body.address);
            }
            break;
            case eMessageBodyType_Voice:
            {
            // 音频SDK会自动下载
            EMVoiceMessageBody *body = (EMVoiceMessageBody *)msgBody;
            NSLog(@"音频remote路径 -- %@"      ,body.remotePath);
            NSLog(@"音频local路径 -- %@"       ,body.localPath); // 需要使用SDK提供的下载方法后才会存在(音频会自动调用)
            NSLog(@"音频的secret -- %@"        ,body.secretKey);
            NSLog(@"音频文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"音频文件的下载状态 -- %lu"   ,body.attachmentDownloadStatus);
            NSLog(@"音频的时间长度 -- %lu"      ,body.duration);
        }
        break;
        case eMessageBodyType_Video:
        {
            EMVideoMessageBody *body = (EMVideoMessageBody *)msgBody;

            NSLog(@"视频remote路径 -- %@"      ,body.remotePath);
            NSLog(@"视频local路径 -- %@"       ,body.localPath); // 需要使用SDK提供的下载方法后才会存在
            NSLog(@"视频的secret -- %@"        ,body.secretKey);
            NSLog(@"视频文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"视频文件的下载状态 -- %lu"   ,body.attachmentDownloadStatus);
            NSLog(@"视频的时间长度 -- %lu"      ,body.duration);
            NSLog(@"视频的W -- %f ,视频的H -- %f", body.size.width, body.size.height);

            // 缩略图sdk会自动下载
            NSLog(@"缩略图的remote路径 -- %@"     ,body.thumbnailRemotePath);
            NSLog(@"缩略图的local路径 -- %@"      ,body.thumbnailRemotePath);
            NSLog(@"缩略图的secret -- %@"        ,body.thumbnailSecretKey);
            NSLog(@"缩略图的下载状态 -- %lu"      ,body.thumbnailDownloadStatus);
        }
        break;
        case eMessageBodyType_File:
        {
            EMFileMessageBody *body = (EMFileMessageBody *)msgBody;
            NSLog(@"文件remote路径 -- %@"      ,body.remotePath);
            NSLog(@"文件local路径 -- %@"       ,body.localPath); // 需要使用SDK提供的下载方法后才会存在
            NSLog(@"文件的secret -- %@"        ,body.secretKey);
            NSLog(@"文件文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"文件文件的下载状态 -- %lu"   ,body.attachmentDownloadStatus);
        }
        break;

        default:
        break;
    }
}

解析透传消息

-(void)didReceiveCmdMessage:(EMMessage *)cmdMessage{
    EMCommandMessageBody *body = (EMCommandMessageBody *)cmdMessage.messageBodies.lastObject;
    NSLog(@"收到的action是 -- %@",body.action);
}

解析消息扩展属性

-(void)didReceiveCmdMessage:(EMMessage *)cmdMessage{
    // cmd消息中的扩展属性
    NSDictionary *ext = cmdMessage.ext;
    NSLog(@"cmd消息中的扩展属性是 -- %@",ext);
}
// 收到消息回调
-(void)didReceiveMessage:(EMMessage *)message{
    // 消息中的扩展属性
    NSDictionary *ext = message.ext;
    NSLog(@"消息中的扩展属性是 -- %@",ext);
}

自动下载消息中的附件

SDK 接收到消息后,会默认下载:图片消息的缩略图,语音消息的语音,视频消息的视频第一帧。

请先判断你要下载附件没有下载成功之后,在调用以下下载方法,否则 SDK 下载方法会再次从服务器上获取附件。

SDK中提供了三种方法。

1. 同步方法

EMError *error = nil;
EMMessage *aMessage = [[EaseMob sharedInstance].chatManager fetchMessageThumbnail:message progress:nil error:&error];
if (!error) {
    NSLog(@"缩略图下载成功,下载后的message -- %@",aMessage);
}

2. block 异步方法

[[EaseMob sharedInstance].chatManager asyncFetchMessageThumbnail:message progress:nil completion:^(EMMessage *aMessage, EMError *error) {
    if (!error) {
        NSLog(@"缩略图下载成功");
    }
} onQueue:nil];

3. IChatManagerDelegate 异步方法

接口调用:

// 当收到消息时,SDK会自动调用下载缩略图。此处在这里调用只是为了演示用。
[[EaseMob sharedInstance].chatManager asyncFetchMessageThumbnail:message progress:nil];

回调监听:

// 当收到图片或视频时,SDK会自动下载缩略图,并回调该方法,如果下载失败,可以通过
// asyncFetchMessageThumbnail:progress 方法主动获取
-(void)didFetchMessageThumbnail:(EMMessage *)aMessage error:(EMError *)error{
    if (!error) {
        NSLog(@"下载缩略图成功,下载后的message是 -- %@",aMessage);
    }
}

下载消息中的原始附件

SDK 中提供了三种方法。

1. 同步方法

EMError *error = nil;
EMMessage *aMessage = [[EaseMob sharedInstance].chatManager fetchMessage:message progress:nil error:&error];
if (!error) {
    NSLog(@"下载成功,下载后的message是 -- %@",aMessage);
}

2. block 回调方法

[[EaseMob sharedInstance].chatManager asyncFetchMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *error) {
    if (!error) {
        NSLog(@"下载成功,下载后的message是 -- %@",aMessage);
    }
} onQueue:nil];

3. IChatManagerDelegate 异步方法

接口调用:

// 当message中带有附件的时候执行下载(如图片、音频、视频、文件)
[[EaseMob sharedInstance].chatManager asyncFetchMessage:message progress:nil];

回调监听:

/*!
 @method
 @brief 收取消息体对象后的回调
 @discussion 当获取完消息体对象后,此回调会被触发;如果此消息体所在的消息对象在服务器端已被加密,那么下载完成后会自动进行解压
 @param aMessage 要获取的消息对象
 @param error        错误信息
 */
- (void)didFetchMessage:(EMMessage *)aMessage error:(EMError *)error;

消息已送达回执

该回调缺省是关闭的,需要您调用打开方法(只需要在SDK初始化后调用一次即可)。

/*!
@property
@brief 开启消息送达通知(默认是不开启的)
@discussion
*/
[[EaseMob sharedInstance].chatManager enableDeliveryNotification];

SDK提供了已送达回执,当对方收到您的消息后,您会收到以下回调:

/*!
 @method
 @brief 收到"已送达回执"时的回调方法
 @discussion 发送方收到接收方发送的一个收到消息的回执,但不意味着接收方已阅读了该消息
 @param resp 收到的"已送达回执"对象,包括 from、to、chatId等
 @result
 */
- (void)didReceiveHasDeliveredResponse:(EMReceipt *)resp;

消息已读回执

已读回执需要开发者主动调用的。当用户读取消息后,由开发者主动调用方法。

发送已读回执

// 发送已读回执。在这里写只是为了演示发送,在APP中具体在哪里发送需要开发者自己决定。
[[EaseMob sharedInstance].chatManager sendHasReadResponseForMessage:message];

接收已读回执

/*!
 @method
 @brief 收到"已读回执"时的回调方法
 @discussion 发送方收到接收方发送的一个收到消息的回执,意味着接收方已阅读了该消息
 @param resp 收到的"已读回执"对象,包括 from、to、chatId等
 @result
 */
- (void)didReceiveHasReadResponse:(EMReceipt *)resp;

上一页:iOS SDK基础功能

下一页:好友管理