iOS DispatchQueue и невозможно обновить пользовательский интерфейс - PullRequest
0 голосов
/ 02 января 2019

У меня проблемы с обновлением пользовательского интерфейса из основного потока в моем приложении.

У меня есть следующий код.В качестве краткого описания я хочу, чтобы цикл for повторялся один раз каждые A секунд, но я хочу, чтобы вложенный асинхронный блок начинался через 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 либо обновляется по назначению, либо никогда не изменяется.Это кажется мне поведением, которое происходит при обновлении пользовательского интерфейса в фоновом потоке.

recordingPeriod всегда достаточно длинный, чтобы UILabel успел обновить;он обновляется не чаще, чем несколько раз в секунду.

В качестве отступления, если я изменю (1) и (2) на оба вызова, то вызову DispatchQueue.main. вместо (1) кода DispatchQueue.global(), кодвнутри блока (2), кажется, никогда не работает.Разве не все блоки должны быть помещены в основную очередь и выполняться в какой-то момент?

1 Ответ

0 голосов
/ 07 января 2019

Однако фактическая UILabel, по-видимому, обновляется только для некоторых значений 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, но я мог бы предложить отказаться от этого запуска по таймеру обновления пользовательского интерфейса.Например, предполагая, что вы используете AVAudioRecorder, вы можете просто указать продолжительность (например, record(forDuration:)), а затем обновить пользовательский интерфейс в методах delegate для вашего рекордера.

...