In next program SimpleClass
do not call deinit
causing memory leak.
final class SimpleClass {
deinit {
print("deinit SimpleClass")
}
}
struct ContentView: View {
var body: some View {
Text("Empty")
.onAppear {
simple = .init()
}
}
@State private var simple = SimpleClass()
}
Tested on iPhone 12 mini, iOS 16.2, Version 14.2 (14C18).
Am I using @State wrapper wrong?
hi,
how SwiftUI handles the initialization
@State private var simple = SimpleClass()
is not exactly what you expect; this SimpleClass
instance is created for SwiftUI's internal use (i.e., you don't own it) and eventually space in the heap will be created for you that is a reference to it ... you are free to change that reference if you wish to something else, but SwiftUI still has ownership of what it created.
when you replace what you think is the value of simple
with a new instance of SimpleClass
in .onAppear, you are updating the heap reference that you own to a second instance of a SimpleClass
object; but SwiftUI still holds on to the SimpleClass
object that it owns.
your concern about "leaking memory" will eventually be taken care of by SwiftUI when ContentView goes away (remember, SwiftUI frequently discards and recreates View structs and thus does a lot of its own memory management).
in your case, you only have one view at the main level and it is likely the view is never discarded; when the app quits, whatever memory SwiftUI owns will be cleaned up.
so, consider the following code experiment:
(1) it would help if you knew exactly when objects of type SimpleClass
come and go, so i will add an id
as well as an initializer.
final class SimpleClass {
let id = UUID()
init() {
print("initialize SimpleClass, id = \(id.uuidString.prefix(8))")
}
deinit {
print("deinit SimpleClass, id = \(id.uuidString.prefix(8))")
}
}
(2) suppose your ContentView
is not the main view of the app. try this, where the main view is a list of one item, with a navigation link to open your view, which is now renamed to be LeakyView
:
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink(value: "LeakyView") {
Text("Show LeakyView")
}
}
.navigationDestination(for: String.self) {_ in
LeakyView()
}
}
}
}
struct LeakyView: View {
@State private var simple = SimpleClass()
init() {
print("called init of LeakyView")
}
var body: some View {
Text("Empty")
.onAppear {
print("on appear occurs")
simple = .init()
}
}
}
(3) run the app. you'll see that when you navigate to and then back from LeakyView
to the main ContentView
, all SimpleClass
instances will have been released. in my case, the output i received is
(a) tap on navigation link. navigation occurs (yes, SwiftUI created a view struct, discarded it, then created a new one)
initialize SimpleClass, id = 106EB234
called init of LeakyView
deinit SimpleClass, id = 106EB234
initialize SimpleClass, id = 9646766A
called init of LeakyView
on appear occurs
initialize SimpleClass, id = C9026337
(b) tap on Back button. even though it looks like the object with id 9646766A has been orphaned, SwiftUI will give it back when the view goes away.
deinit SimpleClass, id = C9026337
deinit SimpleClass, id = 9646766A
all is in order (!)
hope that helps,
DMG