Here is a very simple code example that reproduces a memory leak when deleting an item in SwiftUI List. This is reproducible on iOS 15 and iOS 16 (at least).
Item's are never deallocated even if they are deleted from the list.
import SwiftUI
class Item: Identifiable {
let id: String
init(id: String) {
self.id = id
}
deinit {
print("deinit")
}
}
struct ContentView: View {
@State private var data = [
Item(id: "1"),
Item(id: "2"),
Item(id: "3")
]
var body: some View {
List {
ForEach(data) { Text($0.id) }
.onDelete { indexes in
data.remove(at: indexes.first!)
}
}
}
}
This seems like a very basic example so I am wondering if anyone has noticed this or has a workaround?
LazyVStack and LazyVGrid exhibit the same behaviour.
The memory leak is because you used a class instead of a struct for your model, SwiftUI hangs onto things it needs for use in its diffing algorithm, since usually these things are values there is no need to worry about memory leaks, however it is a problem if you use classes/objects as you have experienced.
SwiftUI is designed to take advantage of value semantics. This means when there is a change to the
Item, it is also a change to the data array. SwiftUI tracks what@Stategetters are called inbody, sincedatais called from body it knows that when the@Statesetter is called it needs to call body to get the new version of the Views. So a change to item, is a change to the data array, which is a change to the@Stateand then SwiftUI works correctly. However, for this to workItemmust a value type, i.e. a struct, not a class.