上篇文章:Swift 极光IM聊天-发送消息
本文主要说一下关于极光iM消息解析的问题。本文会着重的去说一下极光IM原始Demo
中是如何去解析消息的。当了解了IM
如何解析消息的原理后就可以根据自己的需要去做相应的处理,同时针对不同的第三方IM
也可以去使用此套逻辑去解析一下,不同的地方可能就在于其他IM
的方法存在一些差异这个通过去查看对应的官方SDK
应该都能找到对应的方法。说明:关于其他的第三方的IM没有细致的去查看,所以具体的情况还要自己去实验一下
写在前面:本文主要是针对完全将聊天的功能抽离出来放到自己的项目中去,所以针对官方demo中的一些关于消息的解析或者说是设定规则这里不去做过多的说明
极光iM解析消息的主体代码
获取聊天列表的消息
fileprivate func _loadMessage(_ page: Int) {
printLog("\(page)")
let messages = conversation.messageArrayFromNewest(withOffset: NSNumber(value: jMessageCount), limit: NSNumber(value: 17))
if messages.count == 0 {
//如果没有消息的话就会停止
return
}
var msgs: [JCMessage] = []
for index in 0..<messages.count {
let message = messages[index]
let msg = _parseMessage(message)
msgs.insert(msg, at: 0)
//什么时候发送的消息->这个地方可能每个项目的需求可以自行去看一下
//时间参数为: message.timestamp.intValue
if isNeedInsertTimeLine(message.timestamp.intValue) || index == messages.count - 1 {
let timeContent = JCMessageTimeLineContent(date: Date(timeIntervalSince1970: TimeInterval(message.timestamp.intValue / 1000)))
let m = JCMessage(content: timeContent)
m.options.showsTips = false
msgs.insert(m, at: 0)
}
}
if page != 0 {
minIndex = minIndex + msgs.count
chatView.insert(contentsOf: msgs, at: 0)
} else {
minIndex = msgs.count - 1
chatView.append(contentsOf: msgs)
}
self.messages.insert(contentsOf: msgs, at: 0)
}
_parseMessage(message)
方法
fileprivate func _parseMessage(_ message: JMSGMessage, _ isNewMessage: Bool = true) -> JCMessage {
if isNewMessage {
jMessageCount += 1
}
return message.parseMessage(self, { [weak self] (message, data) in
self?.updateMediaMessage(message, data: data)
})
}
parseMessage(_ delegate: JCMessageDelegate, _ updateMediaMessage: Callback? = nil) -> JCMessage
方法
这个方法是解析聊天消息的核心
的地方
open class JCMessage: NSObject, JCMessageType {
init(content: JCMessageContentType) {
self.content = content
self.options = JCMessageOptions(with: content)
super.init()
}
public let identifier: UUID = .init()
open var msgId = ""
open var name: String = "UserName"
open var date: Date = .init()
open var sender: JMSGUser?
open var senderAvator: UIImage?
open var receiver: JMSGUser?
open var content: JCMessageContentType
public let options: JCMessageOptions
open var updateSizeIfNeeded: Bool = false
open var unreadCount: Int = 0
open var targetType: MessageTargetType = .single
open var contentType: JMSGContentType = .text
open var jmessage: JMSGMessage?
}
extension JMSGMessage {
typealias Callback = (JMSGMessage, Data?) -> Void
func parseMessage(_ delegate: JCMessageDelegate, _ updateMediaMessage: Callback? = nil) -> JCMessage {
var msg: JCMessage!
let currentUser = JMSGUser.myInfo()
let isCurrent = fromUser.isEqual(to: currentUser)
let state = self.ex.state
switch(contentType) {
case .text:
if ex.isBusinessCard {
let businessCardContent = JCBusinessCardContent()
businessCardContent.delegate = delegate
businessCardContent.appKey = ex.businessCardAppKey
businessCardContent.userName = ex.businessCardName
msg = JCMessage(content: businessCardContent)
} else {
let content = self.content as! JMSGTextContent
msg = JCMessage(content: JCMessageTextContent(text: content.text))
}
case .image:
let content = self.content as! JMSGImageContent
let imageContent = JCMessageImageContent()
imageContent.imageSize = content.imageSize
if ex.isLargeEmoticon {
imageContent.imageSize = CGSize(width: content.imageSize.width / 3, height: content.imageSize.height / 3)
}
if state == .sending {
content.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
imageContent.upload?(percent)
}
}
imageContent.delegate = delegate
msg = JCMessage(content: imageContent)
if let path = content.thumbImageLocalPath {
let image = UIImage(contentsOfFile: path)
imageContent.image = image
msg.content = imageContent
} else {
content.thumbImageData({ (data, id, error) in
if let data = data {
if let updateMediaMessage = updateMediaMessage {
updateMediaMessage(self, data)
}
}
})
}
case .eventNotification:
let content = self.content as! JMSGEventContent
let noticeContent = JCMessageNoticeContent(text: content.showEventNotification())
msg = JCMessage(content: noticeContent)
msg.options.showsTips = false
case .voice:
let content = self.content as! JMSGVoiceContent
let voiceContent = JCMessageVoiceContent()
voiceContent.duration = TimeInterval(content.duration.intValue)
voiceContent.delegate = delegate
msg = JCMessage(content: voiceContent)
content.voiceData({ (data, id, error) in
if let data = data {
voiceContent.data = data
}
})
case .video:
printLog("video message")
let content = self.content as! JMSGVideoContent
let videoContent = JCMessageVideoContent()
videoContent.delegate = delegate
msg = JCMessage(content: videoContent)
if state == .sending {
content.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
videoContent.uploadVideo?(percent)
}
}
if let path = content.videoThumbImageLocalPath {
let url = URL(fileURLWithPath: path)
let thumbData = try! Data(contentsOf: url)
videoContent.image = UIImage(data: thumbData)
}else{
content.videoThumbImageData { (data, id, error) in
if let data = data {
if let updateMediaMessage = updateMediaMessage {
updateMediaMessage(self, data)
}
}
}
}
videoContent.videoContent = content;
case .file:
printLog("file message")
let content = self.content as! JMSGFileContent
if ex.isShortVideo {
let videoContent = JCMessageVideoContent()
videoContent.delegate = delegate
videoContent.videoFileContent = content;
msg = JCMessage(content: videoContent)
} else {
let fileContent = JCMessageFileContent()
fileContent.delegate = delegate
fileContent.fileName = content.fileName
fileContent.fileType = ex.fileType
fileContent.fileSize = ex.fileSize
if let path = content.originMediaLocalPath {
let url = URL(fileURLWithPath: path)
fileContent.data = try! Data(contentsOf: url)
}
msg = JCMessage(content: fileContent)
}
case .location:
let content = self.content as! JMSGLocationContent
let locationContent = JCMessageLocationContent()
locationContent.address = content.address
locationContent.lat = content.latitude.doubleValue
locationContent.lon = content.longitude.doubleValue
locationContent.delegate = delegate
msg = JCMessage(content: locationContent)
case .prompt:
let content = self.content as! JMSGPromptContent
let noticeContent = JCMessageNoticeContent(text: content.promptText)
msg = JCMessage(content: noticeContent)
msg.options.showsTips = false
default:
msg = JCMessage(content: JCMessageNoticeContent.unsupport)
}
if msg.options.alignment != .center {
msg.options.alignment = isCurrent ? .right : .left
if self.targetType == .group {
msg.options.showsCard = !isCurrent
}
}
msg.targetType = MessageTargetType(rawValue: self.targetType.rawValue)!
msg.msgId = self.msgId
msg.options.state = state
if isCurrent {
msg.senderAvator = UIImage.getMyAvator()
}
msg.sender = fromUser
msg.name = fromUser.displayName()
msg.unreadCount = getUnreadCount()
msg.contentType = contentType
msg.jmessage = self
return msg
}
}
以上是极光iM中关于聊天消息解析的核心的几个方法。方法从上到下是关联在一块的。其实在发送消息和收到别人给你发的消息的时候都会有关于消息Model的封装和解析的方法。但考虑到获取消息列表是最核心的地方,所以就取了这一部分的代码作为一个参考。关于发送消息的封装
和收到消息的解析
这一部分可以去参考Demo中的代码也可以根据上面的代码去自定义封装一下这个消息。
消息的具体解析方法
-
_loadMessage(_ page: Int)
方法
此方主要是调用了极光IM的获取历史消息的接口,返回的数据是一个[JMSGMessage]
格式的数组,然后通过遍历的方式去解析出对应的数据来。然后在循环中添加了是否需要添加时间的这么一个判断,关于这个时间的判断后期看看情况专门的在去补充说明一下这个东西。在Demo
中也有一套现成的方法,其实可以直接拿过来使用也是可以的。
page
参数,我没有去细致的查看这个page
参数是干嘛用的,因为我在实际的使用中没有用到这个参数,所以此处就不在对这个参数去说明了,如果有哪位大佬用过这个参数请告知一二,在此先谢过了。
jMessageCount
参数,这个参数是一个比较重要的参数,在下面的方法中也会用到这个参数。这个参数的第一点
用法就类似于我们上拉加载更多
的一个功能。具体的话可以去看一下SDK
中获取历史消息的方法,对这个参数有具体的说明。第二点
算是一个补充说明,就是在我们发送完消息或者是接收到了一条新的消息以后这个计数都是需要去+1
的。举一个很简单的例子,假如说我现在有20条历史消息
,我先获取了10条历史消息,这时候我发送一条消息或者是接收一条消息后,呢我在页面中实际是显示了11
条消息,同时历史消息变成了21
条。呢我在获取剩下的消息的时候其实应该还是获取10
条未看的历史消息。相信说到这里应该就会明白了为什么每次发送或者是接收一条新消息后需要+1
了。 -
func _parseMessage(_ message: JMSGMessage, _ isNewMessage: Bool = true)
方法
这个方法中主要说一下设置的isNewMessage
这个参数。他在发送和受到消息后会将这个参数设置成true
然后计数+1
,其余的时候都不需要去设置了。因为其余的时候都是在解析历史消息。 -
parseMessage(_ delegate: JCMessageDelegate, _ updateMediaMessage: Callback? = nil) -> JCMessage
主要来说一下这个方法,这个方法就是解析消息的核心的地方
先来说明一下为什么这个地方有一个block
的回调,这个block
主要是为了图片消息、语音消息、视频消息等准备的。因为我们在第一次接收到别人发送的消息的时候返回的message
中是没有对应的图片信息、语音信息、视频信息的,都需要我们去调用对应的方法获取到信息以后然后才能去使用,所以此处设置了这么一个block
回调,说白了他就是为了让你获取到对应的信息后去刷新列表用的(主要是针对图片尺寸的显示问题)
文字信息解析
let content = self.content as! JMSGTextContent
msg = JCMessage(content: JCMessageTextContent(text: content.text))
JCMessage
是自定的一个Model
我只在这个方法中去说一下这个model
中的一些参数。后面不在去说了,后面主要是以我自己使用中的一些封装去说一下
open class JCMessage: NSObject, JCMessageType {
init(content: JCMessageContentType) {
self.content = content //消息类型->(语音、文字等等)
self.options = JCMessageOptions(with: content)/// 消息选项 要显示啥等等。这些可以根据需求去自己做
super.init()
}
public let identifier: UUID = .init()
open var msgId = ""//本地消息id,每条消息在本地都有固定的值 这个会用到
open var name: String = "UserName"
open var date: Date = .init()//一些 图片、音频、视频data等
open var sender: JMSGUser?//用户信息
open var senderAvator: UIImage?
open var receiver: JMSGUser?//用户信息
open var content: JCMessageContentType
public let options: JCMessageOptions
open var updateSizeIfNeeded: Bool = false
open var unreadCount: Int = 0//未读数
open var targetType: MessageTargetType = .single
open var contentType: JMSGContentType = .text
open var jmessage: JMSGMessage?//获取到的原始的信息
}
如果我们想自己去完全定义的话可以采用父类+子类
的方法去实现封装。父类中放公用的一些参数比如文本类型、文本显示在左侧还是右侧等等
,子类中放对应的需要的参数。
比如我的子类在解析数据的时候直接放了一个text
参数在里面,所以我的文本信息解析的方式就改成了如下这样的:
let msg = ChatMessageModel()
msg.msgId = self.msgId
msg.jmsgMessage = self
msg.isRead = self.getUnreadCount() > 0 ? false : true
if message.contentType != .text {
return
}
let content = self.content as! JMSGTextContent
msg.content = ChatMessageTextContent.init(text: content.text)
msg.contentType = .text
/********/
class ChatMessageModel: NSObject {
var contentType : ChatMessageContentType = .unknown
var content : ChatMessageContent = ChatMessageContent()
var alignment : ChatMessageAlignment = .left
var msgId = ""
var jmsgMessage : JMSGMessage?
var isSending = false//是否正在发送
var isRead = false
}
class ChatMessageContent: NSObject {
var height = ChatLayoutInfo.avatarSize
var initHeight = ChatLayoutInfo.avatarSize
override init() {
super.init()
}
init(text : String){
super.init()
}
}
class ChatMessageTextContent: ChatMessageContent {
var text : String
var infoSize = CGSize.zero
override init(text : String) {
self.text = text
self.infoSize = ChatLayoutInfo.getInfoSize(text)
super.init(text: text)
if infoSize.height + ChatLayoutInfo.infoSpace * 2 > 60 {
self.height = infoSize.height + ChatLayoutInfo.infoSpace * 2
}
}
}
图片解析
图片的解析主要是获取到图片的data
数据。如果是别人发送的消息我们第一次获取的时候是需要去调用sdk
方法获取一下,下次再使用的时候就可以直接使用本地的地址去获取了
/*!
* @abstract 获取缩略图的本地路径
*
* @discussion 此属性是通过懒加载的方式获取,必须在下载完成之后此属性值才有意义
*/
@property(nonatomic, strong, readonly) NSString * JMSG_NULLABLE thumbImageLocalPath;
/*!
* @abstract 获取图片消息的缩略图数据
*
* @param handler 结果回调。回调参数:
*
* - data 图片数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion 展示缩略时调用此接口,获取缩略图数据。
* 如果本地数据文件已经存在, 则直接返回;
* 如果本地还没有图片,会发起网络请求下载。下载完后再回调。
*/
- (void)thumbImageData:(JMSGAsyncDataHandler JMSG_NULLABLE)handler;
/*!
* @abstract 获取图片消息的大图数据
*
* @param progressHandler 下载进度。会持续回调更新进度, 直接下载完成。如果为 nil 则表示不关心进度。
* @param handler 结果回调。回调参数:
*
* - data 图片数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion 一般在预览图片大图时,要用此接口。
* 如果本地数据文件已经存在, 则直接返回;
* 如果本地还没有图片,会发起网络请求下载。下载完后再回调。
*/
- (void)largeImageDataWithProgress:(JMSGMediaProgressHandler JMSG_NULLABLE)progressHandler
completionHandler:(JMSGAsyncDataHandler JMSG_NULLABLE)handler;
在解析图片的时候主要是用到上面的三个方法。其中获取高清图是用在查看大图的地方。所以具体的解析逻辑大体上是这样的
let content = self.content as! JMSGImageContent
if let path = content.thumbImageLocalPath{
let image = UIImage(contentsOfFile: path)
保存图片或者是path到自己封装好的model中去
}else{
content.thumbImageData { data, id, error in
if let data = data{
if let updateMediaMessage = updateMediaMessage {
//下载完成缩略图,然后调用block回调去刷新列表
//此时本地已保存了图片地址,下次可以直接使用地址
updateMediaMessage(self, data)
}
}
}
}
语音解析
语音解析主要使用到的一些相关参数
/*!
* @abstract 语音时长 (单位:秒)
*/
@property(nonatomic, copy, readonly) NSNumber *duration;
/*!
* @abstract 获取语音内容的数据
*
* @param handler 结果回调。回调参数:
*
* - data 语音数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion 如果本地数据文件存在, 则直接返回.
* 如果本地还没有语音数据,会发起网络请求下载。下载完后再回调。
*/
- (void)voiceData:(JMSGAsyncDataHandler)handler;
所以在解析语音消息的时候只需要获取到语音时长和语音的data
消息即可
let content = self.content as! JMSGVoiceContent
let voiceContent = ChatMessageVoiceContent()
voiceContent.duration = TimeInterval(content.duration.intValue)//获取语音时长
msg.contentType = .voice
content.voiceData({ (data, id, error) in//获取到语音消息
if let data = data {
voiceContent.data = data//此处只需要保存语音信息即可
//如果语音的UI样式也需要随着某些特定的条件改变高度的话此处可以进行回调信息
}
})
解析小视频消息
视频消息主要使用到了下面的这几个参数
/*!
* @abstract 视频时长 (单位:秒)
*/
@property(nonatomic, copy, readonly) NSNumber *duration;
/*!
* @abstract 视频封面图片大小
*/
@property(nonatomic, assign, readonly) CGSize videoThumbImageSize;
/*!
* @abstract 获取视频封面图片的本地路径
*
* @discussion 此属性是通过懒加载的方式获取,必须在下载完成之后此属性值才有意义
*/
@property(nonatomic, strong, readonly) NSString *JMSG_NULLABLE videoThumbImageLocalPath;
/*!
* @abstract 获取视频消息的封面缩略图
*
* @param handler 结果回调。回调参数:
*
* - data 图片数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion 展示缩略时调用此接口,获取缩略图数据。如果本地数据文件已经存在, 则直接返回; 如果本地还没有图片,会发起网络请求下载。下载完后再回调。
*/
- (void)videoThumbImageData:(JMSGAsyncDataHandler JMSG_NULLABLE)handler;
/*!
* @abstract 获取视频
*
* @param progressHandler 下载进度。会持续回调更新进度, 直接下载完成。如果为 nil 则表示不关心进度。
* @param handler 结果回调。回调参数:
*
* - data 文件数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion 如果本地数据文件已经存在, 则直接返回;如果本地还没有文件,会发起网络请求下载。下载完后再回调。
*/
- (void)videoDataWithProgress:(JMSGMediaProgressHandler JMSG_NULLABLE)progressHandler
completionHandler:(JMSGAsyncDataHandler JMSG_NULLABLE)handler;
其中在获取消息的时候正常只需要获取到视频的缩略图
和视频的大小
就可以了。而视频的data
数据在点击播放的时候在去获取。同时此处也提供了视频第一帧的图片的本地地址,这个和解析图片的本地地址的性质是完全一样的。这里有一个需要注意的地方。因为完整视频的data
是在点击播放视频的时候才去获取的,所以在自定义model的时候此处一定要将获取到的JMSGMessage
保存起来,或者是转化成对应的JMSGVideoContent
保存起来。
具体的解析方法大致如下:
let content = self.content as! JMSGVideoContent
let videoContent = ChatMessageVideoContent()
msg.content = videoContent
msg.contentType = .video
msg.msgId = self.msgId
if let path = content.videoThumbImageLocalPath {
let url = URL(fileURLWithPath: path)
let thumbData = try! Data(contentsOf: url)
videoContent.image = UIImage(data: thumbData)
videoContent.videoContent = content
}else{
content.videoThumbImageData { (data, id, error) in
if let data = data {
if let updateMediaMessage = updateMediaMessage {
updateMediaMessage(self, data)
}
}
}
}
文件解析
关于文件解析存在两种情况。一种是正常的发送文件,另一种是针对iOS和Android相互直接发送信息。在上一篇发送消息的文章中也说到过这个问题。Android端是没有发送视频的功能的,如果要发送视频只能通过发送文件的方式发送出来,而正常的小视频解析都是会带有缩略图的。关于文件解析正常的解析方式只简单的提供一下相关参数,主要说一下如何解析文件格式下的视频信息(类似于iOS端发送视频的显示效果)
正常的文件解析参数
/*!
* @abstract 文件名
*/
@property(nonatomic, copy, readonly) NSString *fileName;
/*!
* @abstract 文件格式
*
* 注意:格式后缀不需要带点,只需后缀名,如:pdf、doc 等
*/
@property(nonatomic, strong) NSString * JMSG_NULLABLE format;
/*!
* @abstract 获取文件内容的数据
*/
- (void)fileData:(JMSGAsyncDataHandler)handler;
/*!
* @abstract 获取文件内容的数据
*/
- (void)fileData:(JMSGAsyncDataHandler)handler;
/*!
* @abstract 获取文件内容的数据
*
* @param progressHandler 下载进度。会持续回调更新进度, 直接下载完成。如果为 nil 则表示不关心进度。
* @param handler 结果回调。回调参数:
*
* - data 文件数据;
* - objectId 消息msgId;
* - error 不为nil表示出错;
*
* 如果 error 为 ni, data 也为 nil, 表示没有数据.
*
* @discussion
* 如果本地数据文件已经存在, 则直接返回;
* 如果本地还没有文件,会发起网络请求下载。下载完后再回调。
*/
- (void)fileDataWithProgress:(JMSGMediaProgressHandler JMSG_NULLABLE)progressHandler
completionHandler:(JMSGAsyncDataHandler JMSG_NULLABLE)handler;
/*!
* @abstract 获取原文件的本地路径
*
* @discussion 此属性是通过懒加载的方式获取,必须在下载完成之后此属性值才有意义
*/
@property(nonatomic, strong, readonly) NSString * JMSG_NULLABLE originMediaLocalPath;
视频类型的文件解析
let content = self.content as! JMSGFileContent
let extras = content.extras
if extras?["fileType"] != nil , extras!["fileType"] as! String == "mp4" {
let videoContent = ChatMessageVideoContent()
videoContent.videoFileContent = content
if content.originMediaLocalPath != nil {
videoContent.image = ChatVM.videoFirstFrame(URL.init(fileURLWithPath: content.originMediaLocalPath!))
}
msg.content = videoContent
msg.contentType = .fileVideo
}else if extras?["video"] != nil , extras!["video"] as! String == "mp4"{
let videoContent = ChatMessageVideoContent()
videoContent.videoFileContent = content
if content.originMediaLocalPath != nil {
videoContent.image = ChatVM.videoFirstFrame(URL.init(fileURLWithPath: content.originMediaLocalPath!))
}
msg.content = videoContent
msg.contentType = .fileVideo
}
此处去判断是否是视频类型的数据需要根据发送视频时设置的扩展参数去判断,主要使用的是extras
这个扩展参数,在发消息的文章中有提到过为什么要在扩展参数中设置video
参数,通过Android
的官方demo
发送视频然后解析数据格式会看到他的定义就是video
这个参数。当然这是基于官方demo
的前提条件下来实现的,如果两端重新定义了发送信息的方法这个参数
也是可以设置成自己想要的规则的。同时也需要保存JMSGFileContent
到自定义model
中去,原因和解析视频消息是一样的,在此不在多说了。
关于文件格式发送视频几点要说明的地方
- 无法直接获取到视频的缩略图
- 视频数据其实就是
fileData
- 无法直接获取到视频时长
针对上述的问题我自己的一些解决方案
正常的视频消息默认都是会显示第一帧的图片的,所以此处在解析完成以后拿到model中的JMSGFileContent
去下载视频(第一次需要下载往后可以直接获取本地地址)
根据返回的视频url
使用AVURLAsset
去获取第一帧的图片。视频时长的方法和获取第一帧的思路是一样的。
自定义消息的解析
自定义消息基本上是完全是自己定义的一些规则,所以此处也没法去说具体要怎么解析,在这里我就拿我自己的项目中自定义消息的规则来举个例子,具体情况请自行根据需求去设计。
let content = self.content as! JMSGCustomContent
let extras = content.extras
let code = extras?["contentType"] as? String ?? ""
if code == "82101" {
let textContent = ChatMessageTextContent.init(text: "你好,我是XXXXX"),您可以查看我的更多简介")
msg.content = textContent
msg.contentType = .text
}
if code == "82102" {
let textContent = ChatMessageTextContent.init(text: "请及时联系我")
msg.content = textContent
msg.contentType = .text
}
说的通俗一点就是在你发送自定义消息时候extras
设置了哪些规则呢么此处就根据定义的规则去判断就可以了,比方说我自己的项目中只定义了一个contentType
参数,呢我就只去解析我自定义的这个contentType
就好了。而关于官方demo
中的一些设计规则大家可以自行的去了解学习一下我这里并你没有使用到官方demo
中的自定义消息。
上一篇:Swift 极光IM聊天-发送消息
下一篇:Swift 极光IM聊天-音视频聊天
关于解析消息的东西大概就讲到这个地方了。当然还有关于定位、名片的这些功能没有说到。关于说到的这两个功能点一个是因为自己项目中没有用到在一个呢是上面的两个功能点完全可以通过自定义消息的方式去实现,所以此处就不在多说了,有兴趣的朋友可以自行学习研究。
同时在这里也欢迎各位大佬对不足的地方提出补充和建议。拍砖就不要了。
网友评论