SwiftUI - passing Binding to NSViewRepresentable causes memory leaks and app slowdown

Viewed 227

I noticed that our macOS app written in SwiftUI becomes slower and slower as the user is using it. Digging into it, I found that SwiftUI keeps allocating small blocks of memory that are never released (and I think it's causing some unnecessary work too).

I isolated a simple macOS app that demonstrates the issue. Sample app: download.

The bug seems to be linked to adding a NSViewRepresentable view in SwiftUI hierarchy and passing to it a Binding to the SwiftUI View's state. Every time the state of SwiftUI view is modified, a small block of memory is allocated that is never released. In the sample app, I just added a timer that updates the state every 10ms to better highlight the issue.

Am I right to believe it's a SwiftUI bug? Looking at my code, I don't think I'm doing anything illegal, do I?

enter image description here

struct ContentView: View {
    
    @State private var point: CGPoint = .zero
    @State private var timer = Timer.publish(every: 0.01, tolerance: nil, on: .main, in: .common).autoconnect()
    
    var body: some View {
        Color.blue.frame(width: 300, height: 300)
            .overlay(Text("\(point.x)"))
            .onSomeExternalAction(someValue: $point)
            .onReceive(timer) { _ in
                point = CGPoint(x: CGFloat(arc4random() % 100), y: 0.0)
            }
    }
}

//////////////////////////////////////////////////////

class _MouseTrackingView: NSView {
   // Just a dummy view   
}

struct MouseTrackingView: NSViewRepresentable {
    typealias NSViewType = _MouseTrackingView
    
    let someValue: Binding<CGPoint>? // Binding is retained here!

    func makeNSView(context: Context) -> _MouseTrackingView {
        return _MouseTrackingView()
    }
    
    func updateNSView(_ nsView: _MouseTrackingView, context: Context) {
        //...
    }
    
}

extension View {
    
    public func onSomeExternalAction(someValue: Binding<CGPoint>?) -> some View {
        self
            .background(MouseTrackingView(someValue: someValue))
    }
}


0 Answers
Related