为什么从后台线程更新 UI 需要这么长时间?
Why does it take such a long time for UI to be updated from background thread?
我知道所有 UI 更新都必须从主线程完成。
但纯粹是为了更深入地了解 GCD 和 dispatch main 是如何工作的:
我有一个按钮 运行 是一个网络调用,在它的 completionHandler 中我最终做了:
self.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.layer.borderWidth = 3.0
颜色变化需要 6-7 秒。显然,如果 运行 以上代码来自主线程,它会立即更改边框颜色。
问题 1 即使我没有任何其他代码 运行,为什么 UI 更改不立即从后台线程发生?还等什么?
有趣的是,如果我单击按钮进行网络调用,然后 点击 文本字段本身(在 6-7 秒之前),边框颜色会改变立即地。
发生这种情况是因为:
我从后台线程更新了模型,即更改排队 UI/view 待更新的 textField 颜色...但是由于我们在后台队列中,UI更新可能需要几秒钟才能完成
但随后我立即点击 textField 并强制从主线程(实际用户触摸始终通过主线程处理)快速读取 textField 及其所有属性,包括边框)...即使在屏幕上还不是红色,但由于它在模型上是红色的,它会从中读取并立即将颜色更改为红色。
问题2:这个观察是否正确?
如果我不点击并等待:
如果我点击:
我的完整代码如下:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBAction func isValid(_ sender: Any) {
let userEmail = textField.text
let requestURL = NSURL(string: "https://jsonplaceholder.typicode.com")
var request = URLRequest(url: requestURL as! URL)
request.httpMethod = "POST"
let postString = "Anything"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
}
do {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let _ = json as? [String: Any] {
self.textField.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.textField.layer.borderWidth = 3.0
}
} catch let error as NSError {
print(error)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
如果您尝试从后台线程执行 UI 更新,"the results are undefined." 我见过的最常见的效果就是您所描述的 - 更新出现之前的延迟很长。我见过的第二个最常见的影响是崩溃。第三常见的效果是某种绘图神器。
从后台线程执行 UI 更新的结果确实是不确定的。您有多个处理器核心同时访问相同的硬件资源,并且这些访问之间的确切时间是不可知且无限可变的。这就像一台没有显示器但有 2 个键盘和 2 个鼠标的计算机,以及 2 个操作员同时编辑同一个文档。每个人在键盘上的操作都会改变文档的状态,并搞砸其他人试图应用的更改。光标会在错误的位置。文档中的文本量将与预期不同。滚动位置将关闭。等等等等
类似地,如果2个核心都试图访问硬件资源来进行屏幕刷新,这些访问将相互交叉和冲突。
正如 Martin 在他的评论中所说,UIKit 代码是专有的,因此我们无法知道 什么 出错的细节。我们所知道的是坏事总会发生,所以不要那样做。
我知道所有 UI 更新都必须从主线程完成。
但纯粹是为了更深入地了解 GCD 和 dispatch main 是如何工作的:
我有一个按钮 运行 是一个网络调用,在它的 completionHandler 中我最终做了:
self.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.layer.borderWidth = 3.0
颜色变化需要 6-7 秒。显然,如果 运行 以上代码来自主线程,它会立即更改边框颜色。
问题 1 即使我没有任何其他代码 运行,为什么 UI 更改不立即从后台线程发生?还等什么?
有趣的是,如果我单击按钮进行网络调用,然后 点击 文本字段本身(在 6-7 秒之前),边框颜色会改变立即地。
发生这种情况是因为:
我从后台线程更新了模型,即更改排队 UI/view 待更新的 textField 颜色...但是由于我们在后台队列中,UI更新可能需要几秒钟才能完成
但随后我立即点击 textField 并强制从主线程(实际用户触摸始终通过主线程处理)快速读取 textField 及其所有属性,包括边框)...即使在屏幕上还不是红色,但由于它在模型上是红色的,它会从中读取并立即将颜色更改为红色。
问题2:这个观察是否正确?
如果我不点击并等待:
如果我点击:
我的完整代码如下:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBAction func isValid(_ sender: Any) {
let userEmail = textField.text
let requestURL = NSURL(string: "https://jsonplaceholder.typicode.com")
var request = URLRequest(url: requestURL as! URL)
request.httpMethod = "POST"
let postString = "Anything"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
}
do {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let _ = json as? [String: Any] {
self.textField.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.textField.layer.borderWidth = 3.0
}
} catch let error as NSError {
print(error)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
如果您尝试从后台线程执行 UI 更新,"the results are undefined." 我见过的最常见的效果就是您所描述的 - 更新出现之前的延迟很长。我见过的第二个最常见的影响是崩溃。第三常见的效果是某种绘图神器。
从后台线程执行 UI 更新的结果确实是不确定的。您有多个处理器核心同时访问相同的硬件资源,并且这些访问之间的确切时间是不可知且无限可变的。这就像一台没有显示器但有 2 个键盘和 2 个鼠标的计算机,以及 2 个操作员同时编辑同一个文档。每个人在键盘上的操作都会改变文档的状态,并搞砸其他人试图应用的更改。光标会在错误的位置。文档中的文本量将与预期不同。滚动位置将关闭。等等等等
类似地,如果2个核心都试图访问硬件资源来进行屏幕刷新,这些访问将相互交叉和冲突。
正如 Martin 在他的评论中所说,UIKit 代码是专有的,因此我们无法知道 什么 出错的细节。我们所知道的是坏事总会发生,所以不要那样做。