Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App freezing when receiving bulk data from Socket. iOS | Socket.io | RealmSwift

I am developing a chatting application where I could receive a number of messages at a time which leads to app freezing. Following is my socket receiver:

func receiveNewDirectMessages() {
    self.socket?.on(EventListnerKeys.message.rawValue, callback: { (arrAckData, ack) in
        print_debug(arrAckData)
        guard let dictMsg = arrAckData.first as? JSONDictionary else { return }
        guard let data = dictMsg[ApiKey.data] as? JSONDictionary else { return }
        guard let chatData = data[ApiKey.data] as? JSONDictionary else { return }
        guard let messageId = chatData[ApiKey._id]  as? String , let chatId = chatData[ApiKey.chatId] as? String else { return }
        if MessageModel.getMessageModel(msgId: messageId) != nil { return }
        let isChatScreen = self.isChatScreen
        let localMsgId = "\(arc4random())\(Date().timeIntervalSince1970)"
        if let senderInfo = data[ApiKey.senderInfo] as? JSONDictionary, let userId = senderInfo[ApiKey.userId] as? String, userId != User.getUserId() {
            _ = AppUser.writeAppUserModelWith(userData: senderInfo)
        }
        let msgModel = MessageModel.saveMessageData(msgData: chatData, localMsgId: localMsgId, msgStatus: 2, seenByMe: false)
        let chatModel = ChatModel.saveInboxData(localChatId: msgModel.localChatId, inboxData: chatData)
        if isChatScreen {
            self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .delivered)
            self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .seen)
        } else {
            ChatModel.updateUnreadCount(localChatId: chatModel.localChatId, incrementBy: 1)
            self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .delivered)
        }
        TabController.shared.updateChatBadgeCount()
    })
}

What's happening above: 1. I am receiving all the undelivered messages ONE-By-ONE in this socket listener. 2. Fetching the message data 3. Saving the received sender's info to Realm DB 4. Saving the message model to realm DB 5. SAVING/UPDATING Chat Thread in realm DB 6. Emitting acknowledgement for the received message 7. Update Chat badge count on tab bar

Below is my emitter for acknowledging the message delivery.

 func emitMessageStatus(msgId: String, chatId: String, socketService: SocketService, status: MessageStatusAction) {

    // Create Message data packet to be sent to socket server
    var msgDataPacket = [String: Any]()
    msgDataPacket[ApiKey.type] = socketService.type
    msgDataPacket[ApiKey.actionType] = socketService.listenerType
    msgDataPacket[ApiKey.data] = [
        ApiKey.messageId: msgId,
        ApiKey.chatId: chatId,
        ApiKey.userId: User.getUserId(),
        ApiKey.statusAction: status.rawValue
    ]
    // send the messsage  data packet to socket server & wait for the acknowledgement
    self.emit(with: EventListnerKeys.socketService.rawValue, msgDataPacket) { (arrAckData) in
        print_debug(arrAckData)
        guard let dictMsg = arrAckData.first as? JSONDictionary else { return }
        if let msgData = dictMsg[ApiKey.data] as? [String: Any] {
            // Update delivered Seen Status here
            if let msgId = msgData[ApiKey.messageId] as? String, let actionType = msgData[ApiKey.statusAction] as? String, let msgStatusAction = MessageStatusAction(rawValue: actionType) {
                switch msgStatusAction {
                case .delivered:
                    if let deliveredTo = msgData[ApiKey.deliveredTo] as? [[String: Any]] {
                        _ = MessageModel.updateMsgDelivery(msgId: msgId, deliveredTo: deliveredTo)
                    }
                case .seen:
                    if let seenBy = msgData[ApiKey.seenBy] as? [[String: Any]] {
                        _ = MessageModel.updateMsgSeen(msgId: msgId, seenBy: seenBy)
                    }
                case .pin:
                    MessageModel.clearPinnedMessages(chatId: chatId)
                    if let pinTime = msgData[ApiKey.pinTime] as? Double {
                        MessageModel.updatePinnedStatus(msgId: msgId, isPinned: true, pinTime: pinTime)
                    }
                case .unPin:
                    if let pinTime = msgData[ApiKey.pinTime] as? Double {
                        MessageModel.updatePinnedStatus(msgId: msgId, isPinned: false, pinTime: pinTime)
                    }
                case .delete:
                    MessageModel.deleteMessage(msgId: msgId)
                case .ackMsgStatus, .like, .unlike:
                    break
                }
            }
        }
    }
}

What's happening above:

  1. Encapsulating all the related information to acknowledge the event
  2. Update realm DB after acknowledgement delivery

Now, I'm not able to defies a perfect threading policy here. What to write in background thread and what should I write in Main thread. I tried doing it however but that leades to random crashes or packet lossses.

Can anyone please lead me forward on this topic. I will be highly grateful.

like image 790
Peeyush karnwal Avatar asked Apr 02 '19 05:04

Peeyush karnwal


1 Answers

  1. Try to use background thread for data processing/ non-UI processing.
  2. Reduce number of updating UI times

    Instead of processing 1 by 1 message, using debounce - like. You can store new messages, then update UI with n new messages. So instead of updating UI/saving data to db 100 times for 100 messages, you can do it 1 time for 100 messages. More detail: with every new message, add it to an array. Call debouncer. The Debouncer will delay a function call, and every time it's getting called it will delay the preceding call until the delay time is over. So after e.g 200ms, if there're no new message, the update func will be call (the callback func that debounce's handling). Then update ui/db with n new stored messages.

    You can group message by time, like group by 1 hour. And then update with delay between each time group. You can do it when the debouncer is called -> group messages by time -> update db/ui by each group. You can use setTimeout, like update group 1, 100ms later update group 2, so the ui won't be freezed

like image 184
tuledev Avatar answered Oct 20 '22 18:10

tuledev