SwiftUI main thread hang detector

This is just a little snippet that is quite useful for reporting when your GUI thread (the main thread / actor) hangs for a significant amount of time. There are numerous heavier-weight tools for analysing this sort of thing, but I’ve found that this simple monitor does what I need most of the time.

var body: some View {
    SomeRootView {

    }.task {
        let approximateGranularity = Duration.milliseconds(10)
        let threshold = Duration.milliseconds(50)

        let clock = SuspendingClock()
        var lastIteration = clock.now

        while !Task.isCancelled {
            try? await Task.sleep(for: approximateGranularity,
                                  tolerance: approximateGranularity / 2,
                                  clock: clock)

            let now = clock.now

            if now - lastIteration > threshold {
                print("Main thread hung for ",
                      (now - lastIteration).formatted(.units(width: .wide,
                                                             fractionalPart: .show(length: 2))),
                      ".",
                      separator: "")
            }

            lastIteration = now
        }
    }
}

You can adjust the two parameters – approximateGranularity and threshold – to suit your preferences. The overhead is quite tiny in CPU-usage terms, although be aware that this will cause the main thread to wake up frequently so it may have a noticeable, detrimental energy-usage impact. I suggest not deploying this to your users.

Perhaps it goes without saying, but a breakpoint set on the print statement enables you to debug deeper into hangs. Even without that, though, it can be illuminating just to have the log message – oftentimes you don’t notice that your app is hanging, because you don’t happen to be actively interacting with it in that moment. But your users might.

1 thought on “SwiftUI main thread hang detector”

Leave a Comment