我在从应用程序的主线程更新 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/