iOS项目开发中实现IM消息体自动解析

引言

在现代直播项目中,IM(即时消息)功能是用户互动的重要组成部分。无论是直播间的聊天、弹幕消息,还是系统通知和互动提示,都需要通过 IM 消息传递给用户。而在直播场景中,消息类型多样,格式复杂,如何高效地解析和展示这些消息,成为了开发中的一个重要问题。

为了提高开发效率,减少手动解析的工作量,自动化的 IM 消息解析机制应运而生。通过自动解析,开发者能够快速识别并处理各种类型的消息,而无需重复编写大量解析逻辑,极大地提升了代码的可维护性和扩展性。

在本文中,我们将深入探讨如何在直播项目中实现 IM 消息的自动解析,分析其实现原理、技术细节以及在实际项目中的应用场景。

IM的消息结构

在直播项目中,IM 消息数据可以看作是一个特殊的网络请求返回的数据。与普通网络请求不同的是,IM 消息不需要我们主动发起请求,而是会自动送达给用户。每一条 IM 消息都携带着独一无二的标识,用来表示消息的类型和用途。例如,当直播间内发生 PK 画面暂停时,系统会通过一条特殊的 IM 消息通知所有用户。该消息的数据结构可能如下所示:

{
    "body": "{ \"liveId\":16177029795,\"pauseLiveId\":16177029794,\"action\":\"pkStreamPause\"}",
    "channel": 0,
    "code": 0,
    "groupId": 16177029795,
    "mType": -1,
    "mid": "6000001263252494",
    "rid": 16349097,
    "role": 0,
    "rtm_mid": 7591435857520568,
    "rtm_type": 120,
    "sendTime": 1736755387085,
    "sid": 100,
    "source": "rtm",
    "streamId": 16177029795,
    "streamerId": 0,
    "type": 10,
    "version": 0
}

整个消息的原始数据很多,但并不是所有的数据我们都需要关心,其中最重要的应该就是body消息体,它是一个json字符串的形式,里面有我们所需要的所有数据。其中包含了一个action字段,该字段由客户端双端,或者客户端与服务端共同定义,表示此条消息数据的用途。

除此之外,在我们的直播间业务场景消息体中可能还会包含operate字段。

{
    "body": "{\"operate\":\"quit\",\"liveId\":16177027891,\"action\":\"videoChat\",\"guestUid\":16349097,\"pos\":1,\"quitReason\":\"MasterBlockChat\"}",
    "channel": 0,
    "code": 0,
    "groupId": 16177027891,
    "mType": -1,
    "mid": "6000000210753483",
    "rid": 16349097,
    "role": 0,
    "rtm_mid": 4915880367602969,
    "rtm_type": 106,
    "sendTime": 1734079831595,
    "sid": 100,
    "source": "rtm",
    "streamId": 16177027891,
    "streamerId": 0,
    "type": 10,
    "version": 0
}

而与body同级的数据,我们可以认为全都是公共参数,其中rtm_type字段,当消息体内没有action字段且没有operate字段时,我们就使用rtm_type字段当做消息标识,而其公共字段我们几乎不需要关心。

实现消息自动解析

既然每一条消息都有一个我们已定义的唯一标识如(action、operate、rtm_type),我们就可以通过这个标识来提前进行消息的注册。具体来说,在应用启动时,我们可以通过一个字典或映射关系,将每种类型的消息标识与对应的数据模型进行关联。这使得在收到消息时,能够根据消息的标识快速查找对应的数据模型,并进行自动解析。

举个例子,假设我们的应用中有多种类型的消息:直播间 PK 暂停、用户发言、系统通知等,每一种消息类型都会有一个对应的解析模型。收到消息时,我们可以通过消息的唯一标识找到对应的解析类,然后将消息的body部分传入该解析类进行解析,从而将原始数据转换成可供使用的对象或结构。

构建消息唯一标识

在实际开发中,IM 消息的唯一标识并不总是由单一字段来决定。例如,在上面的例子中,不同的消息可能通过action、operate和rtm_type等多个字段组合构成唯一标识。因此,我们需要构建一个能够根据这些字段动态生成唯一标识的结构体。

我们可以定义一个结构体MWMessageIdentifer,并通过自定义构造方法来初始化它。该结构体将接受action、operate和rtm_type等字段,将它们合并成一个唯一的标识符,用于区分不同类型的消息。

具体实现如下:

public struct MWMessageIdentifer : Equatable,Hashable {
    let key:String
    fileprivate let kind:MWMessageKind
    fileprivate var actionString:String?
    fileprivate(set) var operateString:String?
    fileprivate var bussinessType:MWRtmBussinessType?
  
    public init(rtmType: MWRtmBussinessType?, rtmAction: String?, operate: String?) {
        self.kind = .rtm
        self.actionString = rtmAction
        self.operateString = operate
        self.bussinessType = rtmType
        self.key = Self.getKey(kind: self.kind, actionString: rtmAction, operateString: operate, bussinessType: rtmType)
    }
    
    public static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.key == rhs.key
    }
    
    fileprivate static func getKey(kind:MWMessageKind,actionString:String?,operateString:String?,bussinessType:MWRtmBussinessType?) -> String {
        return "\(kind.rawValue),\(actionString ?? ""),\(operateString ?? ""),\(bussinessType?.rawValue ?? -999)"
    }
    
    public func hash(into hasher: inout Hasher) {
        hasher.combine(self.key)
    }
}

构建消息体数据

为了保证消息体的数据模型能够与消息的唯一标识一一对应,我们定义一个协议,所有IM的消息体数据模型都应该遵守此协议,并提供协议要求提供的属性。

消息体协议如下:

import ObjectMapper

public protocol MWMessageMapProtocol : Mappable {
    static var linkMsgIdentifiers: [MWMessageIdentifer] { get }
}

协议要求所有的IM消息体数据模型都需要提供一个消息的唯一标识数组。

以第一条PK暂停消息为例,该IM消息的数据模型构建应该如下:

import ObjectMapper


class MWPKPauseMessage: MWMessageMapProtocol {
    
//    { \"liveId\":16177029795,\"pauseLiveId\":16177029794,\"action\":\"pkStreamPause\"}
    static var linkMsgIdentifiers: [MWLinkModule.MWMessageIdentifer] {
        return [MWMessageIdentifer.init(rtmType: nil, rtmAction: "pkStreamPause", operate: nil)]
    }
    
    /// 直播间id
    var liveId: Int?
    /// 暂停直播间id
    var pauseLiveId: Int?
    
    required init?(map: ObjectMapper.Map) {
        
    }
    
    func mapping(map: ObjectMapper.Map) {
        liveId <- map["liveId"]
        pauseLiveId <- map["pauseLiveId"]
    }
}

注册IM消息

有了IM消息体的数据模型之后,我们首先执行注册操作,也就是需要在消息分发出构建一张表,将消息的唯一标识和对应的消息体数据模型一一进行映射。注册这一步就是说将已有的数据模型添加到这张映射表中。

        let models:[MWMessageMapProtocol.Type] = [
            ....
            
            // pk直播间暂停消息
            MWPKPauseMessage.self,
            // pk直播间恢复消息
            MWPKResumeMessage.self,
              ....
        ]
        MWLinkHelper.shared.addDispatchDelegate(delegate: self, registerModels: models,receiveType: .registerdModels)

而在addDispatchDelegate方法中会进行消息的唯一标识提取已经注册操作。

    //注册的所有消息模型dict
    fileprivate lazy var registeredAllModels:[MWMessageIdentifer:Mappable.Type] = [:]
 if let registerModelDict = registerModelDict, registerModelDict.count > 0 {
            registerModelDict.forEach { (key, value) in
                if let oldValue = self.registeredAllModels[key], "\(value)" != "\(oldValue)" {
                    MWAssert(false, "对同一条消息使用了不同model去解析")
                }
                self.registeredAllModels.updateValue(value, forKey: key)
            }
        }

接收IM消息并自动解析

当我们接收到一条 IM 消息时,首先需要根据消息中的action、operate和rtm_type构建一个MWMessageIdentifer结构体的实例。接着,我们利用该实例来获取对应的数据模型类,并创建数据模型的实例,最后将解析后的数据模型进行分发,供应用中的其他模块使用。

以RTM消息为例:

   //接收到rtm消息
    fileprivate func receivedRtmMsg(msg:MWImCustomRtmAttachment,fromSource:MWLinkSourceChannel) {
        DispatchQueue.global().async {
            let newMessage = MWLinkBaseMessage()
            newMessage.groupId = msg.groupId
            newMessage.sendTime = msg.sendTime * 1000
            newMessage.sid = msg.sid
            newMessage.rid = 0
            newMessage.mid = "\(msg.mid)"
            
            newMessage.content = msg.msg
            
            var source = MWMessageRtmSource()
            source.bussinessType = MWRtmBussinessType.init(rawValue: msg.bussinessType) ?? .unknown
            source.contentDict = (newMessage.content ?? "").jsonDict()
            source.actionString = source.contentDict?["action"] as? String
            source.operateString = source.contentDict?["operate"] as? String
            newMessage.sourceRtm = source
            newMessage.msgKind = .rtm
            if newMessage.groupId == 0, let liveId = source.contentDict?["liveId"] as? Int {
                newMessage.groupId = liveId //容个错
            }
            
            self.receiveAllConvertedMsg(groupId: newMessage.groupId ?? 0, message: newMessage,fromSource: fromSource)
        }
    }
  1. 接收到消息后解析消息的公共参数,以及消息的消息体为json格式。
  2. 设置消息的action、operate以及rtm_type。
  3. 处理消息。
fileprivate func receiveAllConvertedMsg(groupId:Int,message:MWLinkBaseMessage,fromSource:MWLinkSourceChannel) {
        DispatchQueue.global().async {
            let message = self.maperObject(from: message)
  ...
}
  /// 消息数据解析
    fileprivate func maperObject(from message:MWLinkBaseMessage) -> MWLinkBaseMessage {
        guard message.mappedModel == nil else {return message}
        var json:[String:Any]?
        if message.msgKind == .link {
            json = message.sourceLink?.contentDict
        }else if message.msgKind == .rtm {
            json = message.sourceRtm?.contentDict
        }
        guard let json = json else {return message}
        
        var identifer:MWMessageIdentifer?
        if message.msgKind == .link, let action = message.sourceLink?.actionString {
            identifer = MWMessageIdentifer.init(linkAction: action)
            let map = self.maperObject(identifer: identifer, json: json)
            message.mappedModel = map
.....
    fileprivate func maperObject(identifer:MWMessageIdentifer?, json:[String:Any]) -> Mappable? {
        guard let identifer = identifer else {return nil}
        if let mappableType = self.registeredAllModels[identifer] {
            let model = mappableType.init(JSON: json)
            return model
        }
        return nil
    }

这一系列流程完成之后我们接收到IM消息的中的消息体就会被构建为一个数据模型,并赋值给message的mappedModel属性。

结语

在本文中,我们深入探讨了如何在直播项目中实现 IM 消息的自动解析。通过构建消息的唯一标识符、映射数据模型以及自动解析消息体,我们有效地提高了消息处理的灵活性和效率。借助于结构化的数据模型和自动化的解析流程,我们能够在面对复杂的 IM 消息时,更加高效地进行处理和扩展。

这种自动解析机制不仅减少了大量的手动解析逻辑,也使得新消息类型的加入变得简单而直观。随着项目的不断扩展和消息类型的增多,使用这样的设计模式能够确保代码的高可维护性和低耦合性,从而提供更加稳定和流畅的用户体验。

在未来,随着 IM 消息种类和复杂度的不断增加,自动解析技术将更加重要。我们可以进一步优化数据模型,增加对多种格式的支持,甚至结合机器学习等技术,智能化地识别和解析消息内容。自动化解析将成为高效、可扩展的应用开发中不可或缺的一部分。

通过本文的实践,希望能够为你在直播项目中实现 IM 消息解析提供一些启发与帮助,也欢迎大家在实际开发中继续探索与优化这一技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值