千亿级IM独立开发指南丨全球即时通讯全套代码4小时速成(三):App内部流程与逻辑(下)

本文篇幅较长,预计阅读时长1-2h,欢迎收藏+点赞+关注。

这是《千亿级IM独立开发指南!全球即时通讯全套代码4小时速成》的第三篇:《APP 内部流程与逻辑》
系列文章可参考:

千亿级IM独立开发指南丨全球即时通讯全套代码4小时速成(一):功能设计与介绍

千亿级IM独立开发指南丨全球即时通讯全套代码4小时速成(二):UI设计与搭建

千亿级IM独立开发指南丨全球即时通讯全套代码4小时速成(三):App内部流程与逻辑(上)

千亿级IM独立开发指南丨全球即时通讯全套代码4小时速成(四):服务端搭建与总结

三、App内部流程与逻辑

7. 会话窗口消息展示

进入会话窗口前,需要先准备好本地已保存的历史消息。然后显示窗口界面的同时,异步线程开始拉取最新的历史消息,并且检查之前的历史消息是否有缺失,有缺失就会持续填补,直到填补完成,或者用户退出等引发的填补中断。

编辑 IMCenter.swift,加入 showDialogueView() 函数:

    class func showDialogueView(contact: ContactInfo) {
        
        //-- 仅能在主线程中调用
        IMCenter.viewSharedInfo.targetContact = contact
        
        IMCenter.viewSharedInfo.strangerContacts.removeAll()
        
        IMCenter.prepareDialogueMesssageInfos(contact: contact)
        
        IMCenter.viewSharedInfo.lastPage = IMCenter.viewSharedInfo.currentPage
        
        IMCenter.viewSharedInfo.currentPage = .DialogueView
    }

并修改 ContactItemView.swift 中 onTapGesture 行为为:

        ... ...

        .onTapGesture {
            IMCenter.showDialogueView(contact: contactInfo)
        }

        ... ...

showDialogueView() 主要清理上一个(如果存在)会话的相关数据,并调用 prepareDialogueMesssageInfos() 函数,准备新的会话数据,然后显示会话窗口界面。

7.1 准备会话窗口数据

prepareDialogueMesssageInfos() 函数先加载本地保存的历史数据,并按时间排序;然后启动两个异步线程,一个清理未同步的未知联系人信息,一个检查并不断填充历史消息。

prepareDialogueMesssageInfos() 代码如下:

    private class func soreChatMessages(messages: [ChatMessage]) -> [ChatMessage] {
        if messages.count <= 1 {
            return messages
        }
        
        return messages.sorted(by: { (m1, m2) -> Bool in
            if m1.mtime < m2.mtime {
                return true
            }
            
            if m1.mtime == m2.mtime {
                if m1.mid < m2.mid {
                    return true
                }
            }
            return false
        })
    }


    class func prepareDialogueMesssageInfos(contact:ContactInfo) {
        
        if contact.kind == ContactKind.Room.rawValue {
            DispatchQueue.global(qos: .default).async {
                IMCenter.client!.enterRoom(withId: NSNumber(value: contact.xid), timeout: 0, success: {
                    
                    DispatchQueue.main.sync {
                        
                        sendCmd(contact: contact, message: "\(getSelfDispalyName()) 进入房间")
                    }

                }, fail: { _ in })
            }
        }
        
        let chatMessages = IMCenter.db.loadAllMessages(contact:contact)
        IMCenter.viewSharedInfo.dialogueMesssages = soreChatMessages(messages: chatMessages)
        
        DispatchQueue.global(qos: .default).async {
            let unknownContacts = pickupUnknownContacts(messages:chatMessages)
            cleanUnknownContacts(unknownContacts: unknownContacts)
        }
        
        DispatchQueue.global(qos: .default).async {
            refillHistoryMessage(contact:contact)
        }
    }

7.2 发送系统通知

当点击的联系人为房间类型时,为了简单起见,直接通过客户端进入房间。这时,我们需要告诉房间中的其他用户,谁进入房间了。因此,我们在这里用 RTM 预定义的 Cmd 类型消息发送系统通知:

    private class func sendGroupCmd(contact:ContactInfo, message:String) {
        IMCenter.client!.sendGroupCmdMessageChat(withId: NSNumber(value: contact.xid), message: message, attrs: "", timeout:0, success: { _ in
        }, fail: {
            _ in
        })
    }
    
    private class func sendRoomCmd(contact:ContactInfo, message:String) {
        IMCenter.client!.sendRoomCmdMessageChat(withId: NSNumber(value: contact.xid), message: message, attrs: "", timeout:0, success: { _ in
        }, fail: {
            _ in
        })
    }

    class func sendCmd(contact:ContactInfo, message:String) {
        if contact.kind == ContactKind.Group.rawValue {
            sendGroupCmd(contact:contact, message:message)
        }else if contact.kind == ContactKind.Room.rawValue {
            sendRoomCmd(contact:contact, message:message)
        } else { return }
        
        objc_sync_enter(locker)
        let mid = fakeMid
        fakeMid += 1
        objc_sync_exit(locker)
        
        let curr = Date().timeIntervalSince1970 * 1000
        
        let chatMessage = ChatMessage(sender: IMCenter.client!.userId, mid: mid, mtime: Int64(curr), message: message)
        chatMessage.isChat = false
        
        var chats = IMCenter.viewSharedInfo.dialogueMesssages
        chats.append(chatMessage)
        
        IMCenter.viewSharedInfo.dialogueMesssages = chats
    }

因为用户发送消息是网络操作,而网络操作可能会失败,比如断网时。因此我们无法等服务器确认后,再将用户发送的内容显示到界面上。但 RTM 只有在返回后,才能获取到消息的 messageId。但显示消息需要使用 MessageId 初始化 ChatMessage 对象。于是我们采用了当年QQ的方法:直接本地显示(这方法现在微信也还在用)。为此,我们引入了一个虚假的MessageId:fakeMid:Int64 进行代替。

但是在其他情况下,比如修改信息或者其他情况,也需要发送系统通知。而且这些系统通知往往是在网络操作完成后,在其他线程内异步触发的。那就存在着 fakeMid 被并发读写的情况。于是我们需要添加一个锁对象 class Locker,并用其进行同步。
于是在 IMCenter.swift 中,增加以下代码:

... ...

class Locker {}

... ...

class IMCenter {
    
    ... ...
    
    static var locker = Locker()
    
    ... ...
    
    static var fakeMid:Int64 = 1
    
    ... ...
}

7.3 同步未知联系人

在一个群组,或者房间中,RTM系统推送过来的消息,只有发送人唯一数字ID,而其它的展示信息,则须要我们自己获取。无论是从我们自己的服务器上获取,还是从RTM服务器上获取。
此外,在获取的过程中,可能因为用户退出等原因,获取过程被中断,此时,App本地的数据中也将存在未知联系人。所以 pickupUnknownContacts() 便是从本地消息中筛查出未知联系人,然后交给 cleanUnknownContacts() 进行信息更新。
相关代码如下:

    private class func syncQueryUsersInfos(type: Int, contacts:[ContactInfo]) {
        
        var queryIds = [NSNumber]()
        for contact in contacts {
            queryIds.append(NSNumber(value: contact.xid))
        }
        
        let attriAnswer = IMCenter.client!.getUserOpenInfo(queryIds, timeout: 0)
        let strangers = decodeAttributeAnswer(type: type, attriAnswer: attriAnswer)
        
        DispatchQueue.main.async {
            for stranger in strangers {
                if let contact = IMCenter.viewSharedInfo.strangerContacts[stranger.xid] {
                    contact.nickname = stranger.nickname
                    contact.imageUrl = stranger.imageUrl
                    contact.showInfo = stranger.showInfo
                } else {
                    IMCenter.viewSharedInfo.strangerContacts[stranger.xid] = stranger
                }
            }
        }
    }

    private class func pickupUnknownContacts(messages:[ChatMessage]) -> [ContactInfo] {
        
        var uids: Set<Int64> = []
        for msg in messages {
            if msg.sender != IMCenter.client!.userId {
                uids.insert(msg.sender)
            }
        }
        
        var contacts = [ContactInfo]()
        if uids.isEmpty == false {
            let allUsers = IMCenter.db.loadAllUserContactInfos()
            
            for uid in uids {
                if let contact = allUsers[uid] {
                    if contact.nickname.isEmpty || contact.imageUrl.isEmpty {
                        contacts.append(contact)
                    }
                } else {
                    contacts.append(ContactInfo(xid: uid))
                }
            }
        }
        
        return contacts
    }

    private class func cleanUnknownContacts(unknownContacts:[ContactInfo]) {
        
        //-- 查询 xname
        var uids = [Int64]()
        for contact in unknownContacts {
            uids.append(contact.xid)
        }
        BizClient.lookup(users: nil, groups: nil, rooms: nil, uids: uids, gids: nil, rids: nil, completedAction: {
            lookupData in
            
            var strangers = [ContactInfo]()
            for (key, value) in lookupData.users {
                let contact = ContactInfo(type: ContactKind.Stranger.rawValue, xid: value)
                contact.xname = key
                
                strangers.append(contact)
            }
            
            DispatchQueue.main.async {
                for stranger in strangers {
                    if let contact = IMCenter.viewSharedInfo.strangerContacts[stranger.xid] {
                        contact.xname = stranger.xname
                    } else {
                        IMCenter.viewSharedInfo.strangerContacts[stranger.xid] = stranger
                    }
                }
            }
        }, errorAction: { _ in })
        
        //-- 查询展示信息
        if unknownContacts.count < 100 {
            syncQueryUsersInfos(type: ContactKind.Stranger.rawValue, contacts: unknownContacts)
        } else {
            var queryContacts = [ContactInfo]()
            
            for contact in unknownContacts {
                queryContacts.append(contact)
                if queryContacts.count == 99 {
                    syncQueryUsersInfos(type: ContactKind.Stranger.rawValue, contacts: queryContacts)
                    queryContacts.removeAll()
                }
            }
            
            if queryContacts.count > 0 {
                syncQueryUsersInfos(type: ContactKind.Stranger.rawValue, contacts: queryContacts)
            }
        }
    }

7.4 填补空缺的历史消息

最后一步,是填补历史消息空缺。填补历史消息空缺其实也很简单。先从数据库中获取已经保存的历史消息检查点,按从新到旧进行排序。然后从新到旧,以10条为单位(RTM的默认限制),降序拉取当前会话的历史消息,然后逐条保存。一旦需要保存的历史消息已经在数据库中存在,则意味着已经填补了历史消息的最新一段空缺。则检查对应范围内是否存在历史消息检查点,如果存在则删除,不存在则以最新的历史消息检查点开始,继续从新到旧,以降序拉取历史消息。
如果这之间发现有未知联系人,则启动新的异步线程进行同步。
填补空缺历史消息的 refillHistoryMessage() 函数及相关代码如下:

    private class func sortHistoryCheckpoint(checkpoints:[HistoryCheckpoint]) -> [HistoryCheckpoint] {
        if checkpoints.count < 2 {
            return checkpoints
        }
        
        return checkpoints.sorted(by: { (c1, c2) -> Bool in
            if c1.ts > c2.ts {
                return true
            }
            if c1.ts == c2.ts {
                return c1.desc
            }
            return false
        })
    }

    private class func refillHistoryMessage(contact:ContactInfo) {
        var historyAnswer: RTMHistoryMessageAnswer? = nil
        var begin: Int64 = 0
        var end: Int64 = 0
        var lastId: Int64 = 0
        
        let fetchCount = 10
        let nsXid = NSNumber(value: contact.xid)
        let nsCount = NSNumber(value: fetchCount)
        
        var checkpoints = db.loadAllHistoryMessageCheckpoints(contact:contact)
        checkpoints = sortHistoryCheckpoint(checkpoints: checkpoints)
        
        while (true)
        {
            if contact.kind == ContactKind.Friend.rawValue {
                historyAnswer = IMCenter.client!.getP2PHistoryMessageChat(withUserId: nsXid, desc: true, num: nsCount, begin: NSNumber(value: begin), end: NSNumber(value: end), lastid: NSNumber(value: lastId), timeout: 0)
            } else if contact.kind == ContactKind.Group.rawValue {
                historyAnswer = IMCenter.client!.getGroupHistoryMessageChat(withGroupId: nsXid, desc: true, num: nsCount, begin: NSNumber(value: begin), end: NSNumber(value: end), lastid: NSNumber(value: lastId), timeout: 0)
            } else if contact.kind == ContactKind.Room.rawValue {
                historyAnswer = IMCenter.client!.getRoomHistoryMessageChat(withRoomId: nsXid, desc: true, num: nsCount, begin: NSNumber(value: begin), end: NSNumber(value: end), lastid: NSNumber(value: lastId), timeout: 0)
            } else { return }
            
            if historyAnswer != nil && historyAnswer!.error.code == 0 {
                
                var chatMessages = [ChatMessage]()
                for message in historyAnswer!.history.messageArray {
                    
                    if message.messageType == 30 {
                        if IMCenter.db.insertChatMessage(type: contact.kind, xid: contact.xid, sender: message.fromUid, mid: message.messageId, message: message.stringMessage, mtime: message.modifiedTime, printError: false) == false {
                            break
                        }
                    } else {
                        if IMCenter.db.insertChatCmd(type: contact.kind, xid: contact.xid, sender: message.fromUid, mid: message.messageId, message: message.stringMessage, mtime: message.modifiedTime, printError: false) == false {
                            break
                        }
                    }
                    
                    let chatMsg = ChatMessage(sender: message.fromUid, mid: message.messageId, mtime: message.modifiedTime, message: message.stringMessage)
                    
                    if message.messageType != 30 {
                        chatMsg.isChat = false
                    }
                    
                    chatMessages.append(chatMsg)
                }
                
                if chatMessages.count > 0 {
                    
                    DispatchQueue.global(qos: .default).async {
                        
                        let unknownContacts = pickupUnknownContacts(messages:chatMessages)
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值