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

1

6

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.

Tapley answered 2/4, 2019 at 5:31 Comment(11)
1. Try to use background thread for data process. 2. Instead of process 1 by 1 message, using debounceInfinitive
@Infinitive Thanks For the response. Can you please elaborate Point no. 2.Tapley
2. Using debounce, you can store new messages, then update ui with n new messages. So instead of update ui/save to db 100 times for 100 messages, you can do it 1 time for 100 messagesInfinitive
@Infinitive That seems to be helpful. Since I was not very well aware of the usage of debounce so I went through a few of the articles. Can you please see if this is what you are talking about: blog.hellocode.co/post/debounce-swift/…Tapley
Yes, it's. Or you can use this github.com/malcommac/Repeat#debouncerInfinitive
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.Infinitive
I'm on it. Thanks for following up. Let me see if it works.Tapley
@Infinitive I've implemented things using this approach. Results are quite impressive for now. But because of the bulk writing of messages in the Realm, the unread counter of messages goes from 1 to 100 at once. I want it to behave like WhatsApp where it seems like the messages are being received one by one and unread counter of messages doesn't increase at a sudden.Tapley
1 by 1 will lead to the low performance because the app have to update ui too many times. If you have 20 threads, each thread have 100 messages, the app have to update 20*100 times. You can group messages by time, like: 1 hours will be update 1 time (doesn't matter if 1 hour has 1 or 100 messages). I see Telegram update message by group of timeInfinitive
For group by times, 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.Infinitive
@Infinitive Can you please help me on this question as well: #55701498Tapley
I
2
  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

Infinitive answered 12/4, 2019 at 3:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.