这段代码中的 'DispatchQueue.main.async' 和 'completed: @escaping () -> ()' 是什么意思?
What does 'DispatchQueue.main.async' and 'completed: @escaping () -> ()' mean in this snippet of code?
基本上这是一个简单的项目,涉及一个表视图,该表视图根据从 JSON 从 api 解析的数据进行更新。我相信 DispatchQueue.main.async
和 completed: @escaping () -> ()
与 updating/reloading tableview 有关,但我不确定它是如何工作的。如果能解释一下这两者的作用,我们将不胜感激。
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()
}
}
两个问题:
属性heroes
与do
块中的局部变量heroes
不是同一个对象。您必须将 let
关键字替换为 self
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data)
并且因为 dataTask
可以创建一个保留循环添加 [unowned self]
来避免这种情况:
URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in ...
在完成块中,您必须重新加载 table 视图。
fetchData() {
print("Success")
tableView.reloadData()
}
语法 DispatchQueue.main.async
正确。 sync
会阻塞线程。
注意:您可以删除勾选if error == nil
。如果guard
成功,error
保证是nil.
最好检查错误而不是data
if let error = error {
print(error)
return
}
do {
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data!)
...
DispatchQueue.main.async { ... }
表示 "run this following code inside the braces on the main queue"(所有 UI 更新必须 运行)。 URLSession
闭包和委托方法 运行 在专用 URLSession
队列上,但 UI (和模型)更新应该发生在主队列上。
仅供参考,将代码分派到另一个队列的两种常见方法是 async
和 sync
。它们非常相似,但是 async
运行 是异步的(即 async
调用之后的代码不会等待主线程完成调用 completed
后再继续) , 和 sync
运行s 同步(即它会等待)。在这种情况下,让 URLSession
队列等待主队列完成是没有意义的,因此 async
是合适的。
completed: @escaping () -> ()
表示:
fetchData
有一个参数,叫做completed
;
- 此参数是 "closure"(即调用者可以提供的匿名代码位;有关详细信息,请参阅 The Swift Programming Language: Closures);
- 这个闭包本身不带任何参数,也不带任何参数returns;和
- 这个闭包"escapes"(意味着到
fetchData
方法returns时它不一定是运行)。
因此,您可以传递要调用的代码块(当您在 dataTask
闭包中看到 completed()
时),如下所示:
fetchData(completed: {
print("Success")
self.tableView.reloadData() // make sure to reload table when request is done
})
但是,您的示例使用 "trailing closure" 语法,其中最终闭包(在本例中是唯一的闭包)可以作为参数省略,并在 fetchData
调用之后添加,导致相同的行为(但语法更简洁):
fetchData {
print("Success")
self.tableView.reloadData()
}
或者,甚至更好:
fetchData { [weak self] in
print("Success")
self?.tableView.reloadData()
}
在一个不相关的观察中,你永远不会更新你的 heroes
属性。至少,你应该这样做:
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()
请注意,您要更新 self.heroes
属性 inside async
闭包以确保您不更新属性 来自后台线程。数组不是线程安全的,通过在主线程上更新 属性,可以避免任何竞争条件。
我可以建议许多其他改进(在 dataTask
中使用 weak
对 self
的引用;向您的 completed
闭包添加一个参数,以便调用者知道是否它成功了,如果不成功则显示警告等),但以上是我建议的最低限度。
基本上这是一个简单的项目,涉及一个表视图,该表视图根据从 JSON 从 api 解析的数据进行更新。我相信 DispatchQueue.main.async
和 completed: @escaping () -> ()
与 updating/reloading tableview 有关,但我不确定它是如何工作的。如果能解释一下这两者的作用,我们将不胜感激。
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()
}
}
两个问题:
属性
heroes
与do
块中的局部变量heroes
不是同一个对象。您必须将let
关键字替换为self
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data)
并且因为
dataTask
可以创建一个保留循环添加[unowned self]
来避免这种情况:URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in ...
在完成块中,您必须重新加载 table 视图。
fetchData() { print("Success") tableView.reloadData() }
语法 DispatchQueue.main.async
正确。 sync
会阻塞线程。
注意:您可以删除勾选if error == nil
。如果guard
成功,error
保证是nil.
最好检查错误而不是data
if let error = error {
print(error)
return
}
do {
self.heroes = try JSONDecoder().decode([HeroStats].self, from: data!)
...
DispatchQueue.main.async { ... }
表示 "run this following code inside the braces on the main queue"(所有 UI 更新必须 运行)。URLSession
闭包和委托方法 运行 在专用URLSession
队列上,但 UI (和模型)更新应该发生在主队列上。仅供参考,将代码分派到另一个队列的两种常见方法是
async
和sync
。它们非常相似,但是async
运行 是异步的(即async
调用之后的代码不会等待主线程完成调用completed
后再继续) , 和sync
运行s 同步(即它会等待)。在这种情况下,让URLSession
队列等待主队列完成是没有意义的,因此async
是合适的。completed: @escaping () -> ()
表示:fetchData
有一个参数,叫做completed
;- 此参数是 "closure"(即调用者可以提供的匿名代码位;有关详细信息,请参阅 The Swift Programming Language: Closures);
- 这个闭包本身不带任何参数,也不带任何参数returns;和
- 这个闭包"escapes"(意味着到
fetchData
方法returns时它不一定是运行)。
因此,您可以传递要调用的代码块(当您在
dataTask
闭包中看到completed()
时),如下所示:fetchData(completed: { print("Success") self.tableView.reloadData() // make sure to reload table when request is done })
但是,您的示例使用 "trailing closure" 语法,其中最终闭包(在本例中是唯一的闭包)可以作为参数省略,并在
fetchData
调用之后添加,导致相同的行为(但语法更简洁):fetchData { print("Success") self.tableView.reloadData() }
或者,甚至更好:
fetchData { [weak self] in print("Success") self?.tableView.reloadData() }
在一个不相关的观察中,你永远不会更新你的
heroes
属性。至少,你应该这样做: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()
请注意,您要更新
self.heroes
属性 insideasync
闭包以确保您不更新属性 来自后台线程。数组不是线程安全的,通过在主线程上更新 属性,可以避免任何竞争条件。我可以建议许多其他改进(在
dataTask
中使用weak
对self
的引用;向您的completed
闭包添加一个参数,以便调用者知道是否它成功了,如果不成功则显示警告等),但以上是我建议的最低限度。