0

I'm getting confused with nested async calls in Swift using Firebase Firestore. I'm trying to make a friends page for my app, and part of the page is a UITableView of groups of the users' friends. I'm storing these groups within a separate collection in Firebase, and attempting to get a list of the groups the current user is in from the document IDs in the group collection. Right now, that looks like this:

func createGroupsArray(completion: @escaping ([Group]?) -> Void) {
        
        let dispatchGroup1 = DispatchGroup()
        let dispatchGroup2 = DispatchGroup()
        var groups = [Group]()
        
        let currentUser = User.current
        
        guard currentUser.groupDocStrings.count > 0 else {
            return completion(nil)
        }
        
        for g in currentUser.groupDocStrings {
            
            dispatchGroup1.enter()
            FirestoreService.db.collection(Constants.Firestore.Collections.groups).document(g).getDocument { (snapshot, err) in
                if let err = err {
                    print("Error retrieving group document: \(err)")
                    return completion(nil)
                } else {
                    let data = snapshot?.data()
                    var friends = [User]()
                    
                    for f in data![Constants.Firestore.Keys.users] as! [String] {
                        dispatchGroup2.enter()
                        FirestoreService.db.collection(Constants.Firestore.Collections.users).document(f).getDocument { (snapshot, err) in
                            if let err = err {
                                print("Error retrieving user document: \(err)")
                                return completion(nil)
                            } else {
                                let uData = snapshot?.data()
                                friends.append(User(uid: f, data: uData!))
                            }
                            dispatchGroup2.leave()
                        }
                    }
                    
                    dispatchGroup2.notify(queue: .main) {
                        let group = Group(groupName: data![Constants.Firestore.Keys.groupName] as! String, friends: friends)
                        groups.append(group)
                    }
                    
                    dispatchGroup1.leave()
                }
            }
            
        }
        
        dispatchGroup1.notify(queue: .main) {
            completion(groups)
        }
        
    }

But of course, when I go to call this function in my tableView(cellForRowAt) function, I can't return a cell because it's asynchronous. I feel like there must be a better way to do this, any help?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
byfu34
  • 61
  • 9

1 Answers1

1

Keep track for every row whether you have data for it or not. Make cellForRowAt() return a cell, with data if you have data, without data if you don't. When you downloaded the data for a row, store the data, remember that you have the data, and invalidate the row. cellForRowAt() will be called again, and this time you fill it with the right data.

Do NOT remember the cell object, because by the time your async call returns, it may not contain the data of the same row anymore. And if you can add or remove rows or change the sort order then do NOT remember the row number, because by the time your async call returns, it may not be the same row number anymore.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Sorry I'm pretty new at this, do you know of any example of what this might look like? – byfu34 Jun 22 '20 at 18:27
  • @byfu34 The thing is, the problem of supplying data asynchronously to a table view is probably _the_ most frequently asked _and answered_ iOS question. There is absolutely no point asking or answering it _yet again_. This is a well-solved problem. All you have to do is search. – matt Jun 22 '20 at 18:35
  • if you are lloking for an example on how to feed the table [here](https://stackoverflow.com/questions/59557113/waiting-until-an-asynchronous-call-is-complete-before-running-the-override-fun/59557221#59557221) is a good one – Soni Sol Jun 23 '20 at 00:12