Search code examples
swiftfirebasegeofire

GeoFire observeReady gets executed prematurely in Swift


In the following function I am am performing an initial geofire search query and wish to process all keys found, append them to an array and send the entire array back.

The issue is that the observeReady block of code is being executed too prematurely and therefore sending an empty array (nothing displays at first load even though there are keys found within the range).

I understand that the observeSingleEvent call is asynchronous and may be causing this behaviour, so my question is, how can I manage this and ensure that the keys are processed prior to executing the handler call within the observeReady block?

func fetchInitialNearbyVenues(deviceLocation: CLLocation, radius: Double, handler: @escaping ([Venue]) -> ()) {

        self.venuesArray.removeAll() 
        var savedByUsers = [String : String]() 

        let query = self.GEOFIRE_VENUES_LOC.query(at: deviceLocation, withRadius: radius) 

            query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in 

                self.REF_VENUES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in 

                  //process snapshot create and append Venue object to array
                  //...
                  //...

                  self.venuesArray.append(venue) //append Venue to array

                })//end observeSingleEvent

            }//end geofire query observe

        query.observeReady {
            handler(self.venuesArray) //PROBLEM: This gets executed prematurely thus sending an empty array via handler 
        }

    }//end func

Solution

  • What you're seeing is expected behavior. The observeReady is guaranteed to fire after all the corresponding observe(.keyEntered) have been called. You can verify this with some simple logging statements:

    query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in 
       print(".keyEntered")
    }
    query.observeReady {
       print(".observeReady")
    }
    

    When you run this it will print:

    .keyEntered

    .keyEntered

    ...

    .observeReady

    That is in line with how the API is supposed to work. But in the .keyEntered you are loading additional data from Firebase, which happens asynchronously. And those calls may indeed complete after the .observeReady has fired.

    So you will need to implement the necessary synchronization yourself. A simple way to detect if you have loaded all the additional data, is to keep a count of all the keys for which you still need to load data. So you +1 that every time you add a key, and -1 every time you've loaded the venue data:

    let venuesToLoadCount = 0
    query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in 
       venuesToLoadCount = venuesToLoadCount + 1
       self.REF_VENUES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in     
            venuesToLoadCount = venuesToLoadCount - 1
            if venuesToLoadCount == 0 {
                print("All done")
            }
        }
    }
    query.observeReady {
        if venuesToLoadCount == 0 {
            print("All done")
        }
    }