Timed events in Swift

I'd like to understand how timed events work in ios/swift. For example, let's say that I want to write a music metronome and I want to trigger a sound in each beat. I understand that there are frameworks like AudioKit, but my question is about timed functions that would help me.

A metronome is a good example because we should have a "tick" in a precise manner.

What I found thus far is the following:

let bpm = 60    
func timed () {
    DispatchQueue.main.asyncAfter(deadline: .now() + (bpm/60)) {
        timed()
        print("Timer fired!")
    }
}

Any tips?

Thank you :)

import Foundation

let bpm = 60

let timer = Timer.scheduledTimer(withTimeInterval: Double(bpm/60), repeats: true) { timer in

// do stuff

}

// you can also cancel the timer

timer.invalidate()

But keep in mind that timer might not be the most precise I am assuming audio kit has more percise abstractions to work with this kind of problems.
Timer also only works on the main roon loop (main thread).

Thanks for looking @filiplazov.
To understand threads in ios, is there a good read for me to get started you might know about?

You rarely work with threads directly when doing iOS. I suggest you read about Grand Central Dispatch which is used a lot when doing iOS and dispatch queues are an abstraction used for performing asynchronous operations.

:flushed::face_with_monocle: You mean Timer.scheduledTimer(withTimeInterval:repeats:block:) Runs in .main, too?

I was following the example At Hacking with Swift with Timer.publish(). The timer drive data update, which in turn cause lots of animations. My app was very unresponsive to touch/swipe. I was sure it’s due to the timer runs on: .main

I looked again at The example code from WWDC 2019 Building Custom Views in SwiftUI. He uses Timer.scheduledTimer(). I changed to this and my app run fine. No more problem.

So what's the different between Timer.publish() vs. Timer.scheduledTimer()? If they both run on .main, then there should NOT be any difference either way, but my app runs better using Timer.scheduledTimer().

I tried Timer.publish(..., on: .init(), ...). Did not work. Nothing change, like no data update.

This thread starts to evolve into a framework question. It'd feel more at home in the Apple Dev Forum. Anyway, here's some documentation about RunLoop.

In general, each thread has a single RunLoop object, retrieved using .current. Sending events to a run loop does nothing, you need to also tell the run loop to handle it using a variation of run() command.

The main thread is a little special that the OS triggers run for you. Now here's the caveat, you should only put user-interactive things like UI update in the main run loop. More importantly, refrain from putting blocking code there, otherwise, it could block the actual UI commands, making the app feels unresponsive even if there's plenty of CPU resources available.

The .scheduleTimer(withTimeInterval:repeats:block:) specifically says to run on the current run loop. So unless you're triggering this from a separated non-ui thread, it should be on the main runloop. I don't know what you did to the published function. Since there's also subscription mechanism involved that I'm not familiar with. Though I don't think it should really affect the performance that much.

This also explain why .published(..., on: .init(), ...) does nothing. You're sending event to newly created, thus unhandled, runloop (Though it's weird that you can actually create a runloop this way).

1 Like

Thanks for sharing @Lantua

Obs: It'd be very nice to have a thread for "ios/swiftui" here in the swift forums. The Apple Dev forum is boring, unfortunately.

1 Like

Problem is both iOS and SwiftUI are private Apple frameworks and operating systems, and policy has been that there are other resources for getting help with the proprietary Apple software.

I would like to say, that (DispatchQueue.main.asyncAfter(deadline:..) very depends on device which it works, so time intervals can vary greatly. The most stable way which works almost same on all devices is the function Date(). It shows same dates in same times.