Search code examples
swiftswiftuigrand-central-dispatch

SwiftUI DispatchQueue asyncAfter stops working correctly after ten scheduled tasks


If I run the code below, it counts correctly to 10 and then skips every odd number.

If you let it run beyond 20, it will start skipping more and more (the output will be like 1-2-3-4-5-6-7-8-9-10-12-14-16-18-20-23-26-29 ...).

Couldn't find documentation of e.g. a limit of scheduled tasks, so asking you :) Learning SwiftUI since yesterday, so pardon if it's an obvious question.

If it's scheduled in a different DispatchQueue than main, it makes no difference.

Thanks for helping!

import SwiftUI

struct ContentView: View {
    @State private var counter : Int = 0
    
    func buttonTap() {
        let time : DispatchTime = .now()
        
        var delay : Double = 0
        
        for _ in 1...50 {
            
            delay = delay + 0.2
            
            DispatchQueue.main.asyncAfter(deadline: time + delay) {
                counter += 1
            }
            
        }
    }
    
    var body: some View {
        ZStack {
            
            Color(.black)
                .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
            
            Text(String(counter))
                .foregroundColor(.green)
                .padding()
            
            Text("Button")
                .foregroundColor(.green)
                .offset(y: 50)
                .onTapGesture(perform: {
                    buttonTap()
                })
            
        }
    }
}

Solution

  • That is an odd behavior, and it even occurs for much longer time intervals between events (for example, 2 seconds).

    Consider using a Timer instead:

    You can use multiple single fire timers that work just like your separate dispatches:

    func buttonTap() {
        
        var delay : Double = 0
        
        for _ in 1...50 {
            
            delay = delay + 0.2
            
            Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { timer in
                self.counter += 1
            }
            
        }
    }
    

    or a single timer that fires every 0.2 seconds:

    func buttonTap() {
        Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { timer in
            self.counter += 1
            if counter == 50 {
                timer.invalidate()
            }
        }
    }