Search code examples
swiftswiftuiwatchos

Timer with DispatchSourceTimer


I have made a timer using DispatchSourceTimer like this:

class Timer: : NSObject, ObservableObject {
    @Published var counterTime: Int = 0
    var timer: DispatchSourceTimer?
    
    func startTimer() {
        let queue = DispatchQueue(label: "com.app.watchkitapp", qos: .userInteractive)
        timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) <--- Xcode point out the error at this line
        timer?.schedule(deadline: .now(), repeating: 1)
        timer?.setEventHandler {
            DispatchQueue.main.async {
                self.counterTime += 1
            }
        }
        timer?.resume()
    }

    func stopTimer() {
        timer?.suspend()
    }

    func resetTimer() {
        timer?.cancel()
        self.counterTime = 0
    }
}

But every time I try to execute the "stopTimer()" function (to stop the timer), I encounter this error:

Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102f34210)

By the way, I want to make a timer that can start, pause and reset in watchos. Thank you!


Solution

  • I saw this error on the stack trace, the document also mentions about it.

    libdispatch.dylib`:
        0x10274521c <+0>:  mov    x8, x0
        0x102745220 <+4>:  stp    x20, x21, [sp, #-0x10]!
        0x102745224 <+8>:  adrp   x20, 6
        0x102745228 <+12>: add    x20, x20, #0x650          ; "BUG IN CLIENT OF LIBDISPATCH: Release of a suspended object"
        0x10274522c <+16>: adrp   x21, 40
        0x102745230 <+20>: add    x21, x21, #0x228          ; gCRAnnotations
        0x102745234 <+24>: str    x20, [x21, #0x8]
        0x102745238 <+28>: str    x8, [x21, #0x38]
        0x10274523c <+32>: ldp    x20, x21, [sp], #0x10
    ->  0x102745240 <+36>: brk    #0x1
    

    It is a programmer error to release an object that is currently suspended, because suspension implies that there is still work to be done. Therefore, always balance calls to this method with a corresponding call to dispatch_resume before disposing of the object. The behavior when releasing the last reference to a dispatch object while it is in a suspended state is undefined.

    I think you're not allowed to release a dispatch_source this way. You can change it like this. And I also recommend validating the timer existence whenever start or stop to avoid initializing multiple times.

    func startTimer() {
        guard timer == nil else { return }
        ...
    }
    
    func stopTimer() {
        timer?.cancel()
        timer = nil
    }
    

    You may want to take a look at this answer.