问题
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).URLSessionclosures and delegate methods run on a dedicatedURLSessionqueue, but UI (and model) updates should happen on the main queue.FYI, the two common methods of dispatching code to another queue are
asyncandsync. They're very similar, butasyncruns asynchronously (i.e. the code after theasynccall won't wait for the main thread to finish callingcompletedbefore continuing), andsyncruns synchronously (i.e. it would wait). In this case, there's no point in having theURLSessionqueue to wait for the main queue to finish, soasyncis 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
fetchDatamethod returns).
So, you could pass a block of code to be called (when you see
completed()in thedataTaskclosure) 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
fetchDatacall, 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
heroesproperty. 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.heroesproperty inside theasyncclosure 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
weakreference toselfindataTask; add a parameter to yourcompletedclosure 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
heroesis not the same object as the local variableheroesin thedoblock. You have to replace theletkeyword withselfself.heroes = try JSONDecoder().decode([HeroStats].self, from: data)and as
dataTaskcan 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