0

I have created a method to fetch user messages from Firebase, however when leaving DispatchGroup app crashes leading to this error Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

I'm not sure what I'm doing wrong. Please help and explain me.

Code:

public func fetchMessages(for userId: String, completion: @escaping (_ result: Result<([Message], [String: Message]), Error>) -> Void) {
        
        let group = DispatchGroup()
        
        var messages = [Message]()
        var messagesDict = [String: Message]()
        
        group.enter()
        database.child("user-messages").child(userId).observe(.childAdded, with: { [weak self] snapshot in
            
            let messageId = snapshot.key
            let messagesRef = self?.database.child("messages").child(messageId)
            
            messagesRef?.observeSingleEvent(of: .value, with: { snapshot in
                
                if let dict = snapshot.value as? [String: AnyObject] {
                    
                    let message = Message(dict: dict)
                    
                    if let chatPartnerId = message.chatPartnerId() {
                        
                        messagesDict[chatPartnerId] = message
                        messages = Array(messagesDict.values)
                        
                        messages.sort { message1, message2 in
                            guard let timestamp1 = message1.timestamp?.intValue, let timestamp2 = message2.timestamp?.intValue else { return false }
                            return timestamp1 > timestamp2
                        }
                        
                        group.leave() // Crashes
                    }
                }
                
            }, withCancel: nil)
            
        }, withCancel: nil)
        
        group.notify(queue: .main) {
            print("Array: \(messages)\nDict: \(messagesDict)")
        }
    }
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Didami
  • 210
  • 2
  • 14
  • What is the purpose of the DispatchGroup in this use case? It appears you're adding an observer with .childAdded, which will iterate over all of the child messages and then loading the message content from the messagesRef. DispatchGroup does not appear to be needed. Also, the enter and leave's are not matching - one group enter and multiple group leave. Lastly, that sort is not well-placed - it's sorting the same array over and over. You should sort the array after everything is loaded. – Jay Feb 25 '22 at 20:13
  • @Jay I am using this method to load a `UICollectionView` using the completion, what should I do? – Didami Feb 25 '22 at 20:17
  • There is no correlation between a Dispatch group, a completion handler and populating a collectionView. Those are three separate operations and while you could tie them together, your intent is not clear. Perhaps you can update your question as to why you're using it we may be able to provide some direction. – Jay Feb 25 '22 at 20:22

1 Answers1

1

It is because you are using observe option. Which probably is notifying you several times. The crash happens due to the fact that you call ‘leave’ without calling ‘enter’ before. I mean you did call ‘enter’. And you do call ‘leave’. But because you observe the completion is probably called more than once. Which will trigger another ‘leave ‘ call while you called the ‘enter’ only once.

You can easily reproduce the crash with this code

import Foundation

let group = DispatchGroup()

group.enter()

group.notify(queue: .main) {
    print("hello")
}

group.leave()
group.leave() // error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x18013d990).

I'm not sure if you need the observe functionality - i.e listen for changes on the object.

Generally, I'd recommend using Firestore - the new (not that new) DB by Firebase. Or follow this guide for getting/setting data from Firebase Realtime Database https://firebase.google.com/docs/database/ios/read-and-write

If you need the "listening" i.e observe feature, I'm not sure how the usage of DispatchGroup helps with your implementation. You would generally use it when you, for example, release 2 (or more) API calls in parallel and want to gather all the information from them. You would create a DispatfchGroup, call enter according to the number of calls you are releasiong, and call leave after you gathered the relevant information.

Something like this

struct AppInfo {
  var items: [items] = []
  var instructors: [Instructors] = []
  var students: [Student] = []
}

func appBootstrapAPICalls(completion: ((AppInfo) -> Void)?) {
  var appInfo = AppIfno()
  let group = DispatchGroup()
  group.enter()
  group.enter()
  group.enter()

  self.fetchItems { items in
    appInfo.items.append(contentsOf: items)
    group.leave()
  }

  self.fetchStudents { students in
    appInfo.students.append(contentsOf: students)
    group.leave()
  }

  self.fetchInstructors { instructors in
    appInfo.instructors.append(contentsOf: instructors)
    group.leave()
  }
  
  group.notify(queue: .main) {
    completion?(appInfo)
  }
}
CloudBalancing
  • 1,461
  • 2
  • 11
  • 22