簡體   English   中英

在 DispatchQueue.main.async 中完成某些處理時,更新 UI 控件出現延遲

[英]Getting a lag in updating UI controls when some processing done in DispatchQueue.main.async

嘗試從 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更好的方法了。

但是您所做的並不完全正確。 您也可以在主線程上執行處理(功能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

主線程是同步的。 在您的示例中,您正在使用循環加載主線程,從而阻止UI更新。

至於您的實際用例-必須在主線程中更新所有UI(否則可能會發生一些不可預測的偽像,包括部分更新和意外的顏色更改)。 GCD(DispatchAsyn)是最自然的方式,但是有許多第三方可以簡化異步操作。 https://cocoapods.org/pods/ResultPromises

當在waitForMilliSecs函數中使用值300時,它看起來是瞬時的,但不是瞬時的。 這只是一個很小的時間,您沒有注意到在代碼在主線程上旋轉時UI已鎖定。

調用self.label1.text = "updated label 1 1111"之后,文本不會立即更新的原因是因為UI更改不會立即發生。 UI以特定速率(60hz或120hz)更新。 您所做的每個更改都將在下一個渲染周期在屏幕上顯示。

等待300毫秒切片期間檢查CPU使用情況。 您會看到它的峰值接近100%,這確實是一件壞事。

您最終想做什么?

您是否嘗試過 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!")
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM