Koorosh
Koorosh

Reputation: 527

Execute Completion Handler in DispatchGroup

I want to show server(s) latency in a UITableView the only problem is ping result handler wont run under DispatchQueue

let dispatch_queue = DispatchQueue(label: "PingQueue", qos: .background)
            dispatch_queue.async {
                let client = SimplePingClient()
                client.pingHostname(hostname: self.Servers[indexPath.row].Address, andResultCallback: { result in
                    guard let r = result as? String else {
                        DispatchQueue.main.async {
                            cell.lblLatency.text = "Timeout"

                        }
                        return
                    }
                    DispatchQueue.main.async{
                        cell.lblLatency.text = r + " ms"
                    }
                })
            }

the andResultCallback wont called at all (i dont know why !?)

Upvotes: 0

Views: 349

Answers (3)

Alexander
Alexander

Reputation: 63271

First, there's a few things you should note about your existing code.

  1. Your call to dispatch_queue.async is doing the client initialization and the invocation of client.pingHostname run on the dispatch_queue.async queue. Note that because pingHostname is an asynchronous API, this returns almost immediately, and doesn't mean that the competition handler will run on dispatch_queue.

    • Note that using dispatch_queue.async probably doesn't make sense. Assuming client initialization is a lightweight, non-IO dependant task (i.e. it doesn't do any network calls of its own), and assuming pingHostname is lightweight (which it almost certainly is, given that it's an async function of its own), then there's no reason why you can't use a dispatch_queue.sync call, or even directly make these calls off your current thread. It'll probably be pretty much just as fast, or perhaps even faster (because async calls make escape analysis much harder, and limit compiler optimization). It has the added benefit of removing the worry of "what if this finished before this next thing? Or what if it's the other way around?".
  2. You provide a closure to the asynchronous pingHostname API, which it has the freedom to run on any thread/queue it wishes, let's call it queue/thread X. The API document will probably shed some light on this.

  3. From the context of queue/thread X, you make async calls into DispatchQueue.main. This is correct; Cocoa is designed to such that all UI updates must always happen from the main thread.

If your stated intention is to run competition handler code on dispatch_queue, then you have to either:

  1. Provide dispatch_queue as a parameter to the pingHostname API, so that it can run the competition handler on your queue, instead of whatever default it resorts to otherwise. Of course, this is something the API must have been designed to have.

  2. Make your own call to dispatch_queue.sync from thread/queue X from within the competition handler.

In the absence of a DispatchQueue parameter, here is how I would write this:

pingQueue = DispatchQueue(label: "PingQueue", qos: .background)

let client = SimplePingClient()
client.pingHostname(
    hostname: self.Servers[indexPath.row].Address, // This is jank, but I'm ignoring it for now
    andResultCallback: { _latency in
        pingQueue.sync {
            print("Do some stuff on pingQueue")

            DispatchQueue.main.sync { // enter main queue for UI updates
                cell.lblLatency.text = (_latency as? String).map { $0 + " ms" } ?? "Timeout"
            }
        }
    }
)

Upvotes: 2

Robert Dresler
Robert Dresler

Reputation: 11150

It's not right to call this inside cellForRowAt. You should have somewhere in your ViewController array of results

var results = [String]()

Now, get results somewhere else, not in cellForRowAt. Create custom method for this, or use custom model.

Also better would be if your pingHostname method returned all results and then you just assign your results array and then reload data of your TableView

client.pingHostname { results in
    if let r = result as? [String] {
        self.results = r
        self.tableView.reloadData()
    } 
}

also use this results array as source for TableView's data source methods

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return results.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    cell.lblLatency.text = results[indexPath.row]
    ...
}

Upvotes: 1

Aaron Brager
Aaron Brager

Reputation: 66252

The result callback isn’t being called because your ping client is being deallocated before it’s finished.

Make client an instance variable instead of a local variable. Set it to nil when you’re done with it.

Upvotes: 0

Related Questions