====== 消息 ====== 消息:IM 交互实体,在 SDK 中对应的类型是 **EMMessage**,EMMessage 可以由多个符合 协议的 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)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)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)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 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; ---- 上一页:[[start:300iosclientintegration:30iossdkbasic|iOS SDK基础功能]] 下一页:[[start:300iosclientintegration:90buddymgmt|好友管理]]