Reputation: 527
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
Reputation: 63271
First, there's a few things you should note about your existing code.
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
.
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?".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.
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:
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.
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
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
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