2

I need to run two functions sequentially before I refresh my collection view. The first function pulls autoids from firebase and writes them into an array. The second function (getLocation) then uses that array to make another call to firebase to retrieve a value (location) beneath each autoid.

I'm using DispatchGroup() to ensure the first function finishes before the second one begins. But I also need the second function to complete before the notify refreshes the collection view.

I've written a basic for loop to test the DispatchGroup() in the second function and was able to get it to execute but I can't get it to work properly when I use it to actually fetch data from firebase.

Wondering if I may be entering and leaving in the wrong place? Here are my functions.

DispatchGroup

let dispatchGroup = DispatchGroup()

Functions:

 func getLocation() {
        self.dispatchGroup.enter()
        for i in 0..<self.eventsArray.count {
                let eventid = self.eventsArray[i]
                self.ref.child("Events").child(eventid).observe(.value, with: { (snapshot) in
                    if let dictionary = snapshot.value as? [String: AnyObject] {

                        let location = dictionary["location"] as! String
                        self.locationsArray.append(location)
                    } })
            }
        self.dispatchGroup.leave()
    }


 func getEvents() {

            self.dispatchGroup.enter()
            Database.database().reference().child("Events").observe(DataEventType.value, with: { (snapshot) in
                if let dictionary = snapshot.children.allObjects as? [DataSnapshot] {
                    for child in dictionary {
                        let eventid = child.key
                        self.eventsArray.append(eventid)

                    }
                     self.dispatchGroup.leave()
                } })  
        }

So I call each function then use dispatchGroup.notify.

  getEvents()
  getLocation()


  dispatchGroup.notify(queue: .main) {
       print(self.locationsArray)
       self.eventsCollectionView.reloadData()
  }

This is the first time I've used DispatchGroups, so any tips or recommendations are welcomed.

KENdi
  • 7,576
  • 2
  • 16
  • 31
Katie
  • 21
  • 7

1 Answers1

0

When I understand your intent correctly, you want to:

  • Get event IDs
  • For each event ID, you want to get the Location

I would suggest to use two different dispatch groups:

  • the first, eventDispatchGroup, will enter before we get new events, and will leave after all the events have been added to the array. When this group gets notified, all the events are in the array, so we call getLocation (NOTE: Will this array ever be cleared / resetted, or will it grow unlimitedly upon subsequent calls, likely with duplicates in it?)
  • In getLocation, we use a second locationDispatchGroup that we enter before we get the locations, and leave after all the locations have been fetched. Upon notification, this group will update the view etc. (NOTE: Will the location array also be cleared at some point in time?)
  • The caller will only have to call getEvents, which then (in the notification block) calls getLocation.

What you'll have to keep in mind is:

  • DispatchGroup.enter() and leave() calls have to be balanced. So if you somehow cannot determine the location to some event, you still have to call leave(), otherwise notify() won't be called
  • Always call enter() outside the observer code, and leave() inside of it, after all data has been processed.

Here is one possible solution:

class A {
    var eventDispatchGroup = DispatchGroup()
    var locationDispatchGroup = DispatchGroup()
    var eventsArray = [String]()
    var locationsArray = [String]()

    func getEvents() {
        // clean self.eventsArray here??
        eventDispatchGroup.enter()
        Database.database().reference().child("Events").observe(DataEventType.value, with: { (snapshot) in
            if let dictionary = snapshot.children.allObjects as? [DataSnapshot] {
                for child in dictionary {
                    let eventid = child.key
                    self.eventsArray.append(eventid)
                }
            }
            eventDispatchGroup.leave();
        })
        eventDispatchGroup.notify(queue: .main) {
            getLocation()
        }
    }

    func getLocation() {
        // clean self.locationsArray here?
        for i in 0..<self.eventsArray.count {
            locationDispatchGroup.enter()
            let eventid = self.eventsArray[i]
            self.ref.child("Events").child(eventid).observe(.value, with: { (snapshot) in
                if let dictionary = snapshot.value as? [String: AnyObject] {

                    let location = dictionary["location"] as! String
                    self.locationsArray.append(location)
                }
                locationDispatchGroup.leave()
            })
        }

        locationDispatchGroup.notify(queue: .main) {
            print(self.locationsArray)
            self.eventsCollectionView.reloadData()
        }
    }

}
Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • Thanks Andreas doAll() didn't work for me, which I'm guessing may be caused by it running getEvents then moving on to getLocations before the getEvents function has finish populating the array that the locations depends on. I'm working on trying the DispatchQueue instead. Going this route, is it best to combine my getEvents and getLocations into one function? – Katie Feb 13 '18 at 06:01
  • Hi @Katie, I just modified my answer. Please check it. – Andreas Oetjen Feb 17 '18 at 18:52