I am using a timer repeating every second to update the now playing currentPlaybackTime
, so I am updating my label every second with the current song's progress.
I used to use an NSTimer
or in Swift a Timer
to repeat every second, however I ran into problems when changing a song or start and stopping a song the seconds label would lag (it'd stay the same for 5 seconds, while you hear the song playing and then would update to the correct time).
Then I went on to use GCD, and I had the same problem, even on a background queue. And then I went to use DispatchSourceTimer and I got the following logs:
Fired: 2018-03-18 13:22:17 +0000
Timer fired: 2018-03-18 13:22:17 +0000
Fired: 2018-03-18 13:22:18 +0000
Timer fired: 2018-03-18 13:22:18 +0000
Fired: 2018-03-18 13:22:19 +0000
Fired: 2018-03-18 13:22:20 +0000
Timer fired: 2018-03-18 13:22:20 +0000
Fired: 2018-03-18 13:22:21 +0000
Fired: 2018-03-18 13:22:22 +0000
Fired: 2018-03-18 13:22:23 +0000
Fired: 2018-03-18 13:22:24 +0000
Fired: 2018-03-18 13:22:25 +0000
Fired: 2018-03-18 13:22:26 +0000
Fired: 2018-03-18 13:22:27 +0000
Fired: 2018-03-18 13:22:28 +0000
Fired: 2018-03-18 13:22:29 +0000
Fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Fired: 2018-03-18 13:22:31 +0000
Timer fired: 2018-03-18 13:22:31 +0000
Fired: 2018-03-18 13:22:32 +0000
Timer fired: 2018-03-18 13:22:32 +0000
The one that says "Fired" is the actual even handler of the DispatchSourceTimer
while the "Timer Fired" is within the handler function that updates the label and such. Clearly you can see that the timer is working fine, but it seems the main thread is heavily used.
Here is my code:
//timer is a DispatchSourceTimer, queue is a concurrent custom queue
public func startTimer() {
timer?.cancel()
timer = DispatchSource.makeTimerSource(queue: queue)
timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1))
timer?.setEventHandler { [weak self] in
print("Fired: \(Date())")
DispatchQueue.main.async {
self?.notifyObservers()
}
}
timer?.resume()
}
public func stopTimer() {
timer?.cancel()
timer = nil
}
And here is notifier observers:
private func notifyObservers() {
for observer: TimeObserver in self.timeObservers {
observer.timerFired()
}
}
I maintain an observer array of objects that want to be notified of the timer firing.
I did a test and removed the for loop and just notified the first observer in the list and it did speed up things a bit, but still did not solve the problem.
if I just play the song and after a few seconds the main thread actually can handle it and I can show the time perfectly, but when doing heavy things like using the MPMediaPlayer
framework to change a song or when calling play()
and pause()
on the MPMusicPlayerController
it starts lagging.
Any help would be appreciated. Thank you.