问题
Basically this is a simple project that involves a tableview that is updated according to data that is parsed from JSON from an api. I believe DispatchQueue.main.async
and completed: @escaping () -> ()
have something to do with updating/reloading the tableview but I'm not sure how it works. Explanation would be appreciated on what these two do.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var heroes = [HeroStats]()
override func viewDidLoad() {
super.viewDidLoad()
fetchData {
print("Success")
}
}
func fetchData(completed: @escaping () -> ()) {
let jsonUrlString = "https://api.opendota.com/api/heroStats"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
if error == nil {
do {
let heroes = try JSONDecoder().decode([HeroStats].self, from: data)
DispatchQueue.main.async {
completed()
}
} catch let error {
print(error)
}
}
}.resume()
}
}
回答1:
The
DispatchQueue.main.async { ... }
means "run this following code inside the braces on the main queue" (which is where all UI updates must run).URLSession
closures and delegate methods run on a dedicatedURLSession
queue, but UI (and model) updates should happen on the main queue.FYI, the two common methods of dispatching code to another queue are
async
andsync
. They're very similar, butasync
runs asynchronously (i.e. the code after theasync
call won't wait for the main thread to finish callingcompleted
before continuing), andsync
runs synchronously (i.e. it would wait). In this case, there's no point in having theURLSession
queue to wait for the main queue to finish, soasync
is appropriate.The
completed: @escaping () -> ()
indicates that:- there is a parameter to
fetchData
, calledcompleted
; - that this parameter is a "closure" (i.e. an anonymous bit of code that the caller can provide; see The Swift Programming Language: Closures for more information);
- this closure that takes no parameters, itself, nor returns anything; and
- this closure "escapes" (meaning that it won't necessarily be run by the time the
fetchData
method returns).
So, you could pass a block of code to be called (when you see
completed()
in thedataTask
closure) like so:fetchData(completed: { print("Success") self.tableView.reloadData() // make sure to reload table when request is done })
But, your example uses "trailing closure" syntax, where the final closure (and in this case, the only closure) can be omitted as a parameter, and just added after the
fetchData
call, resulting in the same behavior (but more concise syntax):fetchData { print("Success") self.tableView.reloadData() }
Or, even better:
fetchData { [weak self] in print("Success") self?.tableView.reloadData() }
- there is a parameter to
In an unrelated observation, you never update your
heroes
property. At the very least, you should do:URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { print(error ?? "Unknown error") return } do { let heroes = try JSONDecoder().decode([HeroStats].self, from: data) DispatchQueue.main.async { self.heroes = heroes completed() } } catch let error { print(error) } }.resume()
Note, you want to update the
self.heroes
property inside theasync
closure to make sure that you don’t update the property from a background thread. Arrays are not thread-safe, and by updating that property on the main thread, you avoid any race conditions.There are many other improvements I could suggest (use
weak
reference toself
indataTask
; add a parameter to yourcompleted
closure so the caller knows if it was successful and display warning if it wasn’t, etc.), but the above is the bare minimum that I’d suggest.
回答2:
Two issues:
The property
heroes
is not the same object as the local variableheroes
in thedo
block. You have to replace thelet
keyword withself
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data)
and as
dataTask
can create a retain cycle add[unowned self]
to avoid that:URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in ...
In the completion block you have to reload the table view.
fetchData() { print("Success") tableView.reloadData() }
The syntax DispatchQueue.main.async
is correct. sync
would block the thread.
Note: You can delete the check if error == nil
. If the guard
succeeds error
is guaranteed to be nil.
It's better to check the error rather than data
if let error = error {
print(error)
return
}
do {
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data!)
...
来源:https://stackoverflow.com/questions/48699048/what-does-dispatchqueue-main-async-and-completed-escaping-mean-in