Oliver Rudzinski
Oliver Rudzinski

Reputation: 15

Reload UITableView after HTTP request has been processed

Let's say I have two classes: One class represents the database where I perform an HTTP GET request.

func getObjects(completion: (([Object]) -> Void)?) {

  guard let url = // ...

  let session = URLSession(configuration: .default)
    let task = session.dataTask(with: url) { (data, response, error) in
      DispatchQueue.main.async {
        if let error = error {
          fatalError("Dispatching main queue failed: \(error)")
        } else if let data = data {
          let decoder = JSONDecoder()

          do {
            let objects = try decoder.decode([Object].self, from: data)
            completion?(object)
          } // ...
    // ...
    }
  task.resume()
}  

The corresponding result object array is passed to a UIViewController which is a different class. Because I instantiate multiple databases based on different configuration objects, I want to display the results sorted in a sectioned UITableView.

func load(then: (([(DatabaseConfig?, [Object])]) -> Void)) {
  var allObjects: [(DatabaseConfig?, [Object])] = [(nil, [])]

  Storage.loadDatabaseConfigs(completion: { configs in
    self.configs = configs
  })

  for config in self.configs {
    var objectSection: (DatabaseConfig?, [Object]) = (nil, [])

    let database = Database.init(config: config)

    objectSection.0 = config
    database.getObjects(completion: { objects in
      objectSection.1.append(objects)
    })
    allObjects.append(allObjectsInSection)
  }

  then(allObjects)
}

The load and "formatting" works.

I call the load(then:) function in viewWillAppear(), where I pass the result to the class array which holds all final entries for the TableView. Then, I try to reload the data.

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(true)
    self.load(then: { objects in
      self.allObjects = objects
      self.tableView.reloadData()
    })
}

I am aware, that the GET request needs its time, so my question is: How or where do I reload the UITableView? I obviously need to be sure, that the GET request has been performed successfully and reload after that. I already tried to envoke DispatchQueue.main.async for the reloading, but that didn't work.

Upvotes: 0

Views: 52

Answers (1)

rodskagg
rodskagg

Reputation: 3937

Since you make several requests, you really don't want to call your then closure until all requests have finished. You could probably use a DispatchGroup for this.

func load(then: (([(DatabaseConfig?, [Object])]) -> Void)) {
    var allObjects: [(DatabaseConfig?, [Object])] = [(nil, [])]

    Storage.loadDatabaseConfigs(completion: { configs in
        self.configs = configs

        let dispatchGroup = DispatchGroup()
        for config in self.configs {
            dispatchGroup.enter()
            var objectSection: (DatabaseConfig?, [Object]) = (nil, [])

            let database = Database.init(config: config)

            objectSection.0 = config
            database.getObjects(completion: { objects in
                objectSection.1.append(objects)
                dispatchGroup.leave()
            })
            allObjects.append(allObjectsInSection)
        }
        dispatchGroup.notify(queue: .main) {
            then(allObjects)
        }
    })
}

EDIT: As @Macistador noted, loadDatabaseConfigs is async, so the database calls should be made in the completion callback of that function.

Upvotes: 2

Related Questions