I am on Swift 4. The goal is to load all the data in an address book, before render the address book in view. In a different language such as js, I may use await in each item in the loop, before telling the view to render the rows. I am looking for the canonical way to solve this issue in Swift 4 with UITableViewController.
Right now the address book is stored in backend with Amplify and GraphQL. I have a User model of form
type User @Model {
  id: ID!
  name: String!
  bio : String!
}
and Contact of form
type Contact @model {
  ownerId: ID!
  userId: ID!
  lastOpened: String
}
In ContactController: UITableViewController.viewDidLoad I fetch all Contact in database where the ownerId is my user's id-token, I then create an object using this contact information. And then for each Contact object instance, I get its corresponding User in database when the object is initialized. Per this post: Wait until swift for loop with asynchronous network requests finishes executing, I am using Dispatch group, and then reload the UITableView after the loop completes and the Dispatch group has ended. But when I print to console, I see that the loop completes before the Contact object has loaded its User information.
Code snippets:
class ContactsController: UITableViewController, UISearchResultsUpdating {
        var dataSource : [Contact] = []
        override func viewDidLoad() {
            super.viewDidLoad()
            let fetchContactGrp = DispatchGroup()
            fetchContactGrp.enter()
            self.getMyContacts(){ contacts in
                for contact in contacts {
                    let _contactData = Contact(
                          userId     : contact.userId
                        , contactId  : contact.id
                        , timeStamp  : contact.timeStamp
                        , lastOpened : contact.lastOpened
                        , haveAccount: true
                    )
                    _contactData.loadData()
                    self.dataSource.append(_contactData)
                }
            }
            fetchContactGrp.leave()
            DispatchQueue.main.async{
                 self.tableView.reloadData()
            }
        }
    }
The function self.getMyContacts is just a standard GraphQL query:
func getMyContacts( callBack: @escaping ([Contact]) -> Void ){
    let my_token = AWSMobileClient.default().username
    let contact = Contact.keys
    let predicate = contact.ownerId == my_token! 
    _ = Amplify.API.query(from: Contact.self, where: predicate) { (event) in
        switch event {
            case .completed(let result):
                switch result {
                    case .success(let cts):
                        /// @On success, output a user list
                        callBack(cts)
                    case .failure(let error):
                        break
                }
            case .failed(let error):
                break
            default:
                break
        }
    }
}
And the Contact object loads the User data from database:
class Contact {
        let userId: String!
        let contactId: String!
        var name : String
        var bio  : String
        var website: String
        let timeStamp: String
        let lastOpened: String
        init( userId: String, contactId: String, timeStamp: String, lastOpened: String, haveAccount: Bool){
            self.userId     = userId
            self.contactId  = contactId
            self.timeStamp  = timeStamp
            self.lastOpened = lastOpened
            self.haveAccount = haveAccount
            self.name = ""
            self.bio  = ""
            self.website = ""
        }
        func loadData(){
            /// @use: fetch user data from db and populate field on initation
            let _ = Amplify.API.query(from: User.self, byId: self.userId) { (event) in
                switch event {
                    case .completed(let res):
                        switch res{
                            case .success (let musr):
                                if (musr != nil){
                                    let userData = musr!
                                    let em    = genEmptyString()
                                    self.name = (userData.name == em) ? "" : userData.name
                                    self.bio  = (userData.bio == em)  ? "" : userData.bio
                                    self.website = (userData.website == em) ? "" : userData.website
                                    print(">> amplify.query: \(self.name)")
                                } else {
                                    break
                                }
                            default:
                               break
                        }
                    default:
                        print("failed")
                }
            }
        }
    }
 
    