Search code examples
swiftfirebasefirebase-realtime-databaseenumeration

Enumerating a collection with nextObject() does not work as intended?


I am fetching some data from my Firebase Storage. The data arrives as a snapshot:

FirebaseDatabase.userMessagesReference?.child(contact.uid).queryOrderedByKey().queryEnding(atValue: lastMessage?.key ?? latestMessage?.key).queryLimited(toLast: 10).observeSingleEvent(of: .value, with: { (messageKeysSnapshot) in
        while let childSnap = messageKeysSnapshot.children.nextObject() as? DataSnapshot {
            print(childSnap)
        }
    }, withCancel: { (error) in
        print(error.localizedDescription)
    })

I'd like to get every child of the children array. Unfortunately, something seems to be wrong, it does not work as expected. Instead of returning every child within the while-loop, the first child is just returned infinite times (and there are at least 10 messages in the database).
Also, when checking the messageKeysSnapshot and printing it to the console (by setting a breakpoint, etc...), I can see that there are several children. I definitely think that there's something wrong with the children enumerator.

How can I get every child once?


Solution

  • messageKeysSnapshot.children returns an NSEnumerator object that knows how to retrieve the objects one by one. The enumerator has its own state and keeps track of what it has given you. The idea is to get the enumerator once, and then call .nextObject() on it repeatedly. The problem is that instead of getting the enumerator just once, you are getting a new copy each time through the loop, which explains why you are seeing the first item over and over again.

    Instead, assign the enumerator to a let and then reference it:

    FirebaseDatabase.userMessagesReference?.child(contact.uid).queryOrderedByKey().queryEnding(atValue: lastMessage?.key ?? latestMessage?.key).queryLimited(toLast: 10).observeSingleEvent(of: .value, with: { (messageKeysSnapshot) in
        let enumerator = messageKeysSnapshot.children
        while let childSnap = enumerator.nextObject() as? DataSnapshot {
            print(childSnap)
        }
    }, withCancel: { (error) in
        print(error.localizedDescription)
    })
    

    Alternatively, you could use a for in loop that works directly with enumerators and use case let to make sure childSnap is a DataSnapshot:

    FirebaseDatabase.userMessagesReference?.child(contact.uid).queryOrderedByKey().queryEnding(atValue: lastMessage?.key ?? latestMessage?.key).queryLimited(toLast: 10).observeSingleEvent(of: .value, with: { (messageKeysSnapshot) in
        for case let childSnap as DataSnapshot in messageKeysSnapshot.children {
            print(childSnap)
        }
    }, withCancel: { (error) in
        print(error.localizedDescription)
    })