Memory Leak & Slow Down in SwiftUI for MacOS

Hi there,

I previously experienced a massive memory leak in SwiftUI on macOS which now seems to be fixed. CLICK

But my app still becomes slower and memory footprint grows after some time of using it. I was able to created a new minimal but more extreme example to reproduce:

This is a full app for SwiftUI. Compile for macOS (I'm using Xcode 12.4 macOS 11.2.3)
Code Block
import SwiftUI
@main
struct DemoApp: App {
    @State var strings = ["Hello 1", "Hello 2"]
    @State var bool = false
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    @State var selected: Int?
    var body: some Scene {
        WindowGroup {
            List(strings.indices, id: \.self, selection: $selected) { stringIndex in
                Text(strings[stringIndex])
            }
            .toolbar(content: {
                ForEach(1...100, id: \.self) { _ in
                    Text("Hello World")
                }
            })
            .onReceive(timer) { input in
                if bool == true { selected = 0 }
                else { selected = 1 }
                bool = !bool
            }
        }
    }
}

Timer is optional but it will show the problem more quickly. It seems to be connected to toolbar content a lot.

Memory footprint quickly goes up, as well as CPU utilization & app slows down significantly just after a few seconds. Please help! What can I do? My app is pretty much not usable because of this bug.

Johannes
Note: If I change the app to this, there's no more leak because now the toolbar doesn't get invalidated and recreated.

Code Block Swift
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
TestView()
.toolbar(content: {
ForEach(1...100, id: \.self) { _ in
Text("Hello World")
}
})
}
}
}
struct TestView: View {
@State var strings = ["Hello 1", "Hello 2"]
@State var bool = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
@State var selected: Int?
var body: some View {
List(strings.indices, id: \.self, selection: $selected) { stringIndex in
Text(strings[stringIndex])
}
.onReceive(timer) { input in
if bool == true { selected = 0 }
else { selected = 1 }
bool = !bool
}
}
}


This is not possible for my original app though, because I need to dynamically switch the toolbar depending on context. This still seems like a pretty significant bug. How can I work around this?
Dear Apple,

please fix this problem. With the simple app above the memory footprint quickly grows to gigabytes and when profiling with the Leak Detector, I see a huger number of leaks in SwiftUI internals. I spent quite some time and resources to build my app where this bug manifests and it prevents me from releasing it. How can such a big problem not be addressed? This is a very minimal and simple app and it still has this huge issue.

Even though this is most noticeable with the Toolbar it manifests itself in other areas. In my app, the amount of leakage is drastically reduced when I remove the toolbar, but it still continues to leak when changing the State.

Johannes

I can confirm that SwiftUI does not clean up something when view's state changes on macOS. In my app, I change view's state on each mouse move and I can see the allocated memory climbing up and up as I move the mouse around.

The worst is that it's not just an issue of the memory. The app is also getting slower and slower - presumably because SwiftUI keep doing unnecessary work.

Sample view:

@State private var hoverX: CGFloat = 0.0

var body: some View {        
   Color.blue.frame(width: 50, height: 50)
     .offset(x: hoverX, y: 0)
     .onMouseMove { point in
        hoverX = point.integral.x
     }
}

Can confirm this, too, which is too bad. If I want to compose a toolbar with items inside the Sidebar section, near the title, and right aligned in the document section, I have no choice but to force the non-sidebar toolbar to reload on every navigation change. It's a waste to rebuild it.

Maybe it's an instrument's ghost, but also a large number of traces do not reference any code... app or Apple.

In the code posted, the id: \.self is a programming mistake, try

ForEach(1..<100) {

I've also noticed this as well in a SwiftUI view using TabView(). Memory will occasionally double for no reason and then if you navigate away and come back to the page it will reset.

Memory Leak &amp; Slow Down in SwiftUI for MacOS
 
 
Q