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?

3
  • why you are having 2 DispatchGroups ? Commented Jun 22, 2020 at 15:03
  • You need to get the data and call reloadData .. and you did not need two groups ... Commented Jun 22, 2020 at 15:04
  • I thought if I only had one group I there would be a problem with the nested for loop. Commented Jun 22, 2020 at 18:29

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Sorry I'm pretty new at this, do you know of any example of what this might look like?
@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.
if you are lloking for an example on how to feed the table here is a good one

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.