При обновлении объекта среды память продолжает расти с каждым обновлением

2020-08-01 swift macos memory-leaks

Я создал новое приложение, представляющее аналогичный случай, который у меня был в реальном приложении. По сути, я обновляю значение наблюдаемого объекта, который используется для визуализации пользовательского интерфейса. И по какой-то причине это заставляет память расти с каждой итерацией. Я также заметил, что объем памяти увеличивается в зависимости от количества итераций в секунду и количества / типов представлений в ContentView.

Примечание. Обновляемый наблюдаемый объект: self.infoObj.text = "Counter: \(counter)"

AppDelgate.swift

func applicationDidFinishLaunching(_ aNotification: Notification) {
    infoObj = InfoObj()

    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView()
        .environmentObject(infoObj)

    // Create the window and set the content view. 
    window = NSWindow(
        contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false)
    window.center()
    window.setFrameAutosaveName("Main Window")
    window.contentView = NSHostingView(rootView: contentView)
    window.makeKeyAndOrderFront(nil)
    
    var counter = 0;
    
    DispatchQueue.global(qos: .userInteractive).async {
        while(true) {
            usleep(1000)
            counter += 1
            DispatchQueue.main.sync {
                self.infoObj.text = "Counter: \(counter)"
            }
        }
    }
}

ContentView.swift

struct ContentView: View {
    @EnvironmentObject var infoObj: InfoObj
    
    var body: some View {
        VStack {
            HStack {
                Text("Text 11")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 12")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 13")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 14")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 15")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            Text(infoObj.text)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            HStack {
                Text("Text 21")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 22")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 23")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 24")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 25")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            HStack {
                Text("Text 31")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 32")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 33")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 34")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 35")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            HStack {
                Text("Text 41")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 42")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 43")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 44")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 45")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            HStack {
                Text("Text 51")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 52")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 53")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 54")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                Text("Text 55")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class InfoObj: ObservableObject {
    @Published var text = "initial"
}

Не могли бы вы помочь мне понять причину, по которой это происходит? И как я могу это решить? Кроме того, почему это не воспринимается как утечка памяти инструментами кода?

Answers

Вы просто циклически выполняете выполнение в стеке, поэтому наблюдаете рост стека (который не обрабатывается как утечка памяти).

Решение состоит в том, чтобы вместо этого сделать асинхронную обработку счетчиков и переместить все в EnvironmentObject

class InfoObj: ObservableObject {
    private var counter = 0
    @Published var text = "initial" {
        didSet {
            print(">> " + text)   // << used for testing
        }
    }

    func runCounter() {
        counter += 1
        text = "Counter: \(self.counter)"

        // !! There is no sense to update UI with 1 milisecond 
        // (but you can tune interval below as you need)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: runCounter)
    }
}

и использовать

    // ... other code
    window.contentView = NSHostingView(rootView: contentView)
    window.makeKeyAndOrderFront(nil)

    infoObj.runCounter()      // << here !!    
}

Related