Как исправить утечку памяти SwiftUI, вызванную составными модификаторами + анимацией? - PullRequest
4 голосов
/ 27 января 2020

Как я могу исправить утечку памяти, которая происходит, когда я использую составные модификаторы с анимацией?

В этом примере у нас есть 4 MySquareView квадрата, которые имеют анимированный эффект вращения, и они находятся внутри ZStack в ContentView, который имеет модификатор масштаба. Как видите, используемая память со временем продолжает увеличиваться.

enter image description here memory leak

Похоже, возникает та же проблема с другими модификаторами тоже. Полный пример:

import SwiftUI

struct MySquare: Identifiable, Hashable {
    var id = UUID()
    var offsetX: CGFloat
    var offsetY: CGFloat
}

struct MySquareView: View {
    @State private var rotateSquare = true
    var body: some View {
        Rectangle()
            .fill(Color.purple)
            .frame(width: 30, height: 30)
            .rotationEffect(.degrees(self.rotateSquare ? -25 : 25))
            .onAppear(perform: {
                withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
                    self.rotateSquare.toggle()
                }
            })
    }
}

struct ContentView: View {


    var mySquares = [
        MySquare(offsetX: -40, offsetY: 40),
        MySquare(offsetX: 50, offsetY: -20),
        MySquare(offsetX: -10, offsetY: 80),
        MySquare(offsetX: 110, offsetY: 20)
    ]

    var body: some View {
        VStack {
            ZStack {
                ForEach(mySquares, id: \.self) { mySquare in
                    MySquareView()
                        .offset(x: mySquare.offsetX, y: mySquare.offsetY)
                }
            }
            .scaleEffect(0.8)

        }
    }
}

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

1 Ответ

3 голосов
/ 27 января 2020

Это, по-видимому, связано с встраиванием мутации в ForEach, которая при просмотре графиков памяти может включать List (который также , похоже, имеет утечку памяти, когда его элементы видоизменяются ). Я рекомендую открыть Отзыв по этому вопросу.

Это можно устранить, удалив ForEach:

func square(at offset: Int) -> some View {
    let mySquare = mySquares[offset]
    return MySquareView()
        .offset(x: mySquare.offsetX,
                y: mySquare.offsetY)
}

var body: some View {
    VStack {
        ZStack {
            square(at: 0)
            square(at: 1)
            square(at: 2)
            square(at: 3)
        }
        .scaleEffect(0.8)
    }
}

Вы также можете взломать этот путь путем внедрения рекурсивного AnyView вместо использования ForEach. Могут быть и другие умные решения, подобные этому; это может стоить исследовать дальше, так как потеря ForEach и List довольно неприятна.

func loopOver<C: Collection, V: View>(_ list: C, content: (C.Element) -> V) -> AnyView
{
    guard let element = list.first else { return AnyView(EmptyView()) }
    return AnyView(Group {
        content(element)
        loopOver(list.dropFirst(), content: content)
    })
}

var body: some View {
    VStack {
        ZStack {
            loopOver(mySquares) { MySquareView().offset(x: $0.offsetX, y: $0.offsetY )}
        }
        .scaleEffect(0.8)
    }
}

(Подобное использование AnyView помешает различным оптимизациям SwiftUI, так что это последнее средство, но в этом на случай, если это, вероятно, необходимо.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...