GCD Timer not calling Event Handler

Setup a GCD timer, the print statement "ABTimer Fired" is being called, but not "ABTimer Fired Block" and I cannot figure out why! Any ideas?
Here is my basic code, comment for clairity:

class ABTimer {
    
    private var timer: DispatchSourceTimer!
    
    /// Creates a timer
    @discardableResult
    init(fire: Date, interval: TimeInterval, repeats: Bool, block: @escaping (ABTimer) -> Void) {
        // Cancels existing timers
        timer?.cancel()
        // Queue a new timer
        let queue = DispatchQueue(label: "com.applebot.timer", attributes: .concurrent)
        // Make the timer
        timer = DispatchSource.makeTimerSource(queue: queue)
//        let i = fire.timeIntervalSince(Date()) // Code for determing fire date
        if repeats {
            // If repeating, schedule a repeating timer
            timer?.schedule(deadline: .now(), repeating: interval, leeway: .milliseconds(100))
        } else {
            // if not repeating, schedule a non-repeating timer
            timer!.schedule(deadline: .now())
        }
        // Set event handler
        timer?.setEventHandler(qos: .background) { [weak self] in
            print("\(Date()) >>>> NOTICE: ABTIMER FIRED BLOCK")
            guard let ss = self else { return }
            block(ss)
        }
        print("\(Date()) >>>> NOTICE: ABTIMER FIRED")
        // Start Timer
        timer?.resume()
    }
}

Accepted Reply

That isn't enough context. If "t" is a local variable in a function or method, then the ABTimer instance it refers to is going to be released as soon as the current scope ends (or sooner, but really soon either way).


If "t" is a property of a custom type, then it's going to keep the ABTimer instance alive as long as the value of that type exists (and so on outwards).


The fact that it's called "t" (and not something meaningful) suggests it's a local variable.


FWIW, doing this in a playground changes the timing, so the ABTimer instance may be staying alive longer than in an app environment. You can't really conclude anything from that.

Replies

How are you calling this initializer? My guess is you're not keeping a strong reference to the ABTimer instance, so it immediately deallocates and the timer is destroyed.


BTW, if you're going to declare your 'timer' property with a "!", then the whole point is that you DON'T need to refer to it as "timer?" or as "timer!". Just "timer" will do.

let t =ABTimer(fire: Date(), interval: 60, repeats: true) { (_) in
}

Called like that, in a .xcodeproj will not call the block. When ran in a playground, it runs BUT the second you add anything to the block, it doesn't run.

That isn't enough context. If "t" is a local variable in a function or method, then the ABTimer instance it refers to is going to be released as soon as the current scope ends (or sooner, but really soon either way).


If "t" is a property of a custom type, then it's going to keep the ABTimer instance alive as long as the value of that type exists (and so on outwards).


The fact that it's called "t" (and not something meaningful) suggests it's a local variable.


FWIW, doing this in a playground changes the timing, so the ABTimer instance may be staying alive longer than in an app environment. You can't really conclude anything from that.

Fun to learn!
You are correct to assume it was in a function. Moving initial decloration outside the function solved the issue! I honestly did not think about it dieing when the function did.

It's a mental shift you have to learn when dealing with asynchronous code using closures. The execution order is no longer the source code order, and you always need to be thinking when things are happening, and when object lifetimes end.