在主线程的密集功能期间 UI 未更新(在 Swift 中)

Posted

技术标签:

【中文标题】在主线程的密集功能期间 UI 未更新(在 Swift 中)【英文标题】:UI not updating (in Swift) during intensive function on main thread 【发布时间】:2022-01-16 00:03:55 【问题描述】:

我想知道是否有人可以就如何在 Swift 的一个特别密集的功能(在主线程上)期间“强制”更新 UI 提供建议。

解释一下:我正在尝试向我的应用程序添加“导入”功能,这将允许用户从备份文件中导入项目(可以是 1 到 1,000,000 条记录之间的任何内容,例如,取决于他们的大小备份)保存到应用程序的 CodeData 数据库。此函数使用“for in”循环(循环遍历备份文件中的每条记录),并且该循环中的每个“for”,该函数都会向委托(ViewController)发送一条消息以更新其 UIProgressBar 进度因此用户可以在屏幕上看到实时进度。我通常会尝试将此密集功能发送到后台线程,并在主线程上单独更新 UI……但这不是一个选项,因为在 CoreData 上下文中创建这些项目必须在主线程上完成(根据当我最初尝试在后台线程上执行 Swift 时出现错误/崩溃),因此我认为这会导致 UI “冻结”而不是在屏幕上实时更新。

代码的简化版本是:

class CoreDataManager 
    
    var delegate: ProgressProtocol?
    
    // (dummy) backup file array for purpose of this example, which could contain 100,000's of items
    let backUp = [BackUpItem]()
    
    // intensive function containing 'for in' loop
    func processBackUpAndSaveData() 
        
        let totalItems: Float = Float(backUp.count)
        var step: Float = 0
        
        for backUpItem in backUp 

            // calculate Progress and tell delegate to update the UIProgressView
            step += 1
            let calculatedProgress = step / totalItems
            delegate?.updateProgressBar(progress: calculatedProgress)
            
            // Create the item in CoreData context (which must be done on main thread)
            let savedItem = (context: context)
        
        // loop is complete, so save the CoreData context
        try! context.save()
    



// Meanwhile... in the delegate (ViewController) which updates the UIProgressView
class ViewController: UIViewController, ProgressProtocol  
    
    let progressBar = UIProgressView()
    
    // Delegate function which updates the progress bar
    func updateProgressBar(progress: Float) 

        // Print statement, which shows up correctly in the console during the intensive task
        print("Progress being updated to \(progress)")

        // Update to the progressBar is instructed, but isn't reflected on the simulator
        progressBar.setProgress(progress, animated: false)
    

需要注意的重要一点:上述代码中的 print 语句运行良好/符合预期,即在整个较长的“for in”循环中(可能需要一两分钟),控制台连续显示所有打印语句(显示增加的进度值),所以我知道委托 'updateProgressBar' 函数肯定是正确触发的,但是屏幕本身上的进度条 根本没有更新 /没有改变……我假设这是因为 UI 被冻结并且没有“时间”(因为需要一个更好的词)来反映考虑到主要功能运行的强度的更新进度。

我对编码比较陌生,所以如果我要求澄清任何回复,请提前道歉,因为这对我来说是新的。如果相关,我使用 Storyboards(而不是 SwiftUI)。

只是真的在寻找关于是否有任何(相对简单的)路线来解决这个问题的任何建议/提示,并且基本上“强制”在这个密集的任务期间更新 UI。

【问题讨论】:

我应该补充一点,为了这篇文章,上面的代码只是作为 example 编写的......所以 UIProgressView 没有 IBOutlet 的事实不是一个问题...对象都与 real 代码中的情节提要正确连接...当然,当不运行这个密集的“for in”循环时,我可以更新应用程序中的进度条 您需要将密集工作移出主队列。 developer.apple.com/documentation/coredata/… 还有developer.apple.com/documentation/coredata/… 谢谢 Paulw11 - 在这种情况下,这肯定是队列/线程管理的事情 - 非常感谢您发送这些链接...非常感谢您的意见 【参考方案1】:

您说“......只是真的在寻找任何建议/提示,以了解是否有任何(相对简单的)路线来解决这个问题,并在这个密集的任务期间基本上‘强制’更新 UI。”

没有。如果你在主线程上同步做耗时的工作,你阻塞了主线程,UI更新直到你的代码返回才会生效。

您需要弄清楚如何在后台线程上运行您的代码。我已经有一段时间没有使用 CoreData 了。我知道在后台线程上进行 CoreData 查询是可能,但我不再记得细节了。这就是你需要做的。

关于您对打印语句的评论,这是有道理的。 Xcode 控制台与您的应用程序的运行循环是分开的,即使您的代码没有返回,它也能够显示输出。然而,应用程序 UI 无法做到这一点。

【讨论】:

谢谢 Duncan C - 这正是我一直在寻找的:这里没有简单/快速的解决方案,这是一个卷起袖子深入线程管理的案例。由于我仍在学习很多这对我来说是新的,所以即使是关于控制台与应用程序的运行循环分开的有用说明也有助于我理解为什么打印语句运行良好(但 UI 没有在屏幕上更新)....非常有用,这正是我需要的答案...非常感谢!

以上是关于在主线程的密集功能期间 UI 未更新(在 Swift 中)的主要内容,如果未能解决你的问题,请参考以下文章

如何在android一条单独线程,更新ui ?

android中如何实现UI的实时更新

网络操作不能直接写在主线程中 以及 为什么不能在子线程中更新UI控件的属性

有关子线程对UI的线程更新的说法

iOS 子线程下载 主线程刷新UI

使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 UI 响应