swift - iOS DispatchQueue 和无法更新 UI

标签 swift user-interface grand-central-dispatch

我在从应用程序的主线程更新 UI 时遇到问题。

我有以下代码。作为简要描述,我希望 for 循环每 A 秒迭代一次,但我希望嵌套异步 block 在每次迭代开始后 B 秒开始。

public func startRecording()
{
    let recordingPeriod = TimeInterval(Float(Constants.windowSize)/Float(Constants.sampleFrequency))
    DispatchQueue.global().async // (1)
    {
        repeat
        {
            for (index, audioRecorder) in self.AudioRecorders.enumerated()
            {
                guard let audioRecorder = audioRecorder else { continue }
                audioRecorder.deleteRecording()
                audioRecorder.record()
                DispatchQueue.main.asyncAfter(deadline: .now() + recordingPeriod) // (2)
                {
                    if let pitch = self.finishSampling(audioRecorder: audioRecorder, index: self.AudioRecorders.index(of: audioRecorder))
                    {
                        print(pitch)
                        self.meterViewController?.updateMeter(string: String(pitch)) // (3)
                    }
                }
                // Use usleep here to pause thread which runs overall repeat loop
                // Sets functional time interval for one loop iteration
                usleep(useconds_t((Float(Constants.windowSize)/Float(Constants.samplesPerWindow))/Float(Constants.sampleFrequency)*1000000))
            }
        }
        while self.keepRecording ?? false
    }
}

其中 (3) 只是更新 UILabel:

func updateMeter(string: String)
{
    if Thread.isMainThread {
        meterLabel.text = string
    } else {
        DispatchQueue.main.sync {
            meterLabel.text = string
        }
    }
}

正如预期的那样,if Thread.isMainThread 语句似乎总是返回 true。但是,实际的 UILabel 似乎仅针对 recordingPeriod 的某些值进行更新。更改 recordingPeriod 的值后,UILabel 要么按预期更新,要么从不更改。在我看来,这就像在后台线程上更新 UI 时会发生的行为。

recordingPeriod 始终足够长,以便 UILabel 有时间更新;它每秒更新不超过几次。

顺便说一句,如果我将 (1) 和 (2) 更改为都调用 DispatchQueue.main. 而不是 (1) 为 DispatchQueue.global(), block (2) 内的代码似乎永远不会运行。难道所有的 block 不都应该放在主队列上并在某个时刻执行吗?

最佳答案

However, the actual UILabel only appears to get updated for some values of recordingPeriod ...

这听起来像是计时器合并(一种省电功能)的情况。考虑这个例子:

let start = CACurrentMediaTime()
for i in 0 ..< 1_000 {
    DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) / 10) {
        let elapsed = CACurrentMediaTime() - start
        print(String(format: "%0.2f", elapsed))
    }
}

前几次迭代看起来不错,但之后您会看到 GCD 将开始将它们合并在一起。

有多种方法可以通过使用 DispatchSourceTimer 来停止这种合并,但我可能建议放弃这种基于计时器的 UI 更新触发。例如。假设您使用的是 AVAudioRecorder,您可以只指定持续时间(例如 record(forDuration:)),然后更新 delegate 中的 UI录音机的方法。

关于swift - iOS DispatchQueue 和无法更新 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53999364/

相关文章:

ios - WKwbview navigationresponse如何获取请求的URL

user-interface - 日期应该格式化在哪一层?

ios - 必须在另一个线程中使用 dispatch_semaphore_t 吗?

ios - 连接 Realm 和 SwiftBond 的最佳方式是什么

ios - Swift - 使用按钮手动滚动 UITextView

wpf - 禁用 WPF 标签加速键(缺少文本下划线)

swift - 如何使用GCD在后台快速上传图片

ios - 如何从 GCD 调度队列中删除排队的 block ?

ios - watchOS 3 应用程序在点击复杂功能后重新启动

java - 在 Java 中使用线程