1

尝试从 DispatchQueue.main.async 闭包更新 UI 控件,该闭包执行一些处理并需要几百毫秒或更长时间,UI 标签的更新会有几秒到几秒的延迟。如果没有延迟或延迟很短,则 UI 中的标签会随着代码的运行而更新,并且似乎是瞬时的。

我有这个小例子来说明我添加了一个“毫秒等待”函数来模拟处理时间并显示发生的 UI 更新滞后的问题。

在示例中,waitForMilliSecs 设置为 300 或更少,标签会立即更新。任何大于 300 的数字,都会出现更新标签几秒到几秒的延迟。日志消息表明代码已经运行,理想情况下应该在打印出来时更新 UI。

class ViewController: UIViewController {

    @IBOutlet weak var label1: UILabel!

    @IBOutlet weak var label2: UILabel!

    override func viewDidLoad() {
       super.viewDidLoad()

        DispatchQueue.main.async {
           os_log("before")
           self.label1.text = "updated label 1 1111"
           self.label2.text = "updated label 2 2222"
           self.waitForMilliSecs(MilliSecs: 300)
           os_log("after")
       }

}

func waitForMilliSecs(MilliSecs millisecs: Int) -> Void {
    var date = NSDate()
    let firstTime = Int64(date.timeIntervalSince1970 * 1000)
    var currentTime = firstTime
    while currentTime - firstTime < millisecs {
        date = NSDate()
        currentTime = Int64(date.timeIntervalSince1970 * 1000)
    }
}

真正的用例是我正在抓取 HTML 页面的数据,然后使用页面的一些内容更新 UI。完成处理程序是从后台线程上的 URLSession.shared.dataTask 调用的,因此 DispatchQueue.main.async 闭包用于更新主线程上的 UI。

有没有更好的方法来更新 UI?有没有办法强制更新主线程上的事件?

4

4 回答 4

1

没有比在主线程上更新 UI 更好的方法了。

但你所做的并不完全正确。您也在主线程上进行处理(函数 waitForMilliSecs)。这可能不是你想要的。您需要在后台线程上进行处理,并在处理完成后,在主线程上更新 UI。

override func viewDidLoad() {
   super.viewDidLoad()
   DispatchQueue.global().async {
       print("before")
       //this function is doing some real work and produces some results.
       self.waitForMilliSecs(MilliSecs: 3000)
       print("after")

       DispatchQueue.main.async {
            self.label1.text = "updated label 1 1111"
            self.label2.text = "updated label 2 2222"
       }
    }
}

github repo 显示整个示例: https ://github.com/jurajantas/TestOfBackgroundProcessing.git

于 2018-01-06T23:36:36.470 回答
0

当您在函数中使用值 300 时waitForMilliSecs,它看起来是瞬时的,但事实并非如此。这只是一个足够小的时间,您不会注意到当您的代码在主线程上旋转时 UI 已锁定。

调用后文本没有立即更新的原因self.label1.text = "updated label 1 1111"是因为 UI 更改不会立即发生。UI 以特定速率(60hz 或 120hz)更新。您所做的每一项更改都将在下一个渲染周期中显示在屏幕上。

在等待时检查 300 毫秒切片期间的 CPU 使用率。你会看到它飙升到接近 100%,这是一件非常糟糕的事情。

你最终想要做什么?

于 2018-01-06T23:49:59.307 回答
0

你试过 CADisplayLink 吗?默认情况下,它每 60 秒调用一次屏幕刷新(这解决了我的一些问题):https ://developer.apple.com/documentation/quartzcore/cadisplaylink

let displayLink = CADisplayLink(target: self, selector: #selector(updateUI))
displayLink.add(to: .current, forMode: .common)

接着:

@objc func updateUI() {
    print("Updating UI!")
}
于 2021-12-15T22:55:21.753 回答
0

主线程是同步的。在您的示例中,您正在使用循环加载主线程,阻止 UI 更新。

至于您的真实用例 - 所有 UI 都必须在主线程中更新(否则可能会发生一些不可预测的伪影,包括部分更新和意外的颜色更改)。GCD (DispatchAsyn) 是最自然的方式,但是许多第三方可用于简化异步操作。喜欢https://cocoapods.org/pods/ResultPromises

于 2018-01-05T17:11:40.900 回答