#SwiftUI knowers: can anyone tell me why an existing Thing instance isn't deinitialzed when thing is set to nil? It's only deinitialized when a new Thing is assigned to thing.

This makes no sense to me. And it's bad if Thing is managing a resource.

#Xcode15 #iOS17

@marksmith Interesting observation! I’m not certain but I suspect it has something to do with SwiftUI’s view diffing - after the old and new states are compared, the old state isn’t immediately released.

Hopefully someone else will have a more definitive answer.

@humblehacker @marksmith 🤷‍♂️If this is true, it's a plain old bug. SwiftUI is allowed to keep the instance until its state management is done, but not to keep potentially large objects in memory forever, after the developer has expressed the wish to discard it.

@groue @humblehacker The behavior is certainly subtle (to me anyway).

If you run the code below in a preview window,
make a Thing with the top button,
then set the state var to nil with the bottom button,
then set it to nil again with the bottom button,
the object only gets deinitialized after you set the state var to nil twice.

@marksmith @groue Yep, seeing the same thing. And if I don’t reference thing in the view (comment out Text), it takes a third tap to get it to deinit.

Oddly, the same code on macOS deinits on the first nil tap, but with Text commented out it takes two taps. So exactly 1 fewer tap for each scenario.

I nested ContentView, and when dismissed everything was cleaned up, so that’s good. But I agree with @groue that this looks like a bug.

Tested in a playground.

@marksmith I'm almost certain that the @State creates a strong reference of it internally

@fsousa Right, but setting it to nil should remove the only strong reference, no?

Here’s what the Memory Graph looks like if you create it, set it to nil, and then stop at a breakpoint just before replacing it with a new one. It’s still alive:

@marksmith Interesting. Looks like the SwiftUI is retaining the instance
@marksmith the State property wrapper binds the lifetime of what it wraps to the lifetime of an onscreen view with a particular identity. It does that in this case by retaining a reference to the Thing instance created the first time a ContentView with a particular identity is added to the view graph SwiftUI is maintaining behind the scenes. Identity is determined explicitly or implicitly by factors like the view's location in the view graph.
@marksmith If you want a view to observe an instance of a reference type whose lifecycle you manage yourself, use the Observable attached macro on your type, create and manage an instance elsewhere, and pass in the instance to the view initializer.

@toddthomas Say I have a stupid simple app with one view that shows a Thing. It has a popup menu that lists the names of different Things. When an item is selected, a new Thing is created and becomes the one displayed.

If I understand you, @State can’t be used to hold the only reference to the current Thing if I want the normal ARC lifecycle. I need some long-lived ThingMaker class instance outside of the view to hold the reference instead. Is that correct?

@marksmith I see State as the tool that gives a Thing instance the lifetime we intuitively expect of a property that belongs to a "view”. We're thinking of the lifetime of the view on screen, which is very different from the lifetime of a View-protocol-implementing struct value which lives briefly on the stack as SwiftUI creates them many times to render and update the view on screen. So I think State is what you want for a view that shows one Thing at a time.
@marksmith I'm away from Xcode right now, will make an example later today.
@marksmith I think this example shows how the State property wrapper allows the Thing instance to persist as long as the user desires, but not be leaked, even though many new ThingPicker values are created by SwiftUI as it updates the view graph. Let me know if I’ve misunderstood the question you were asking. 😅
@marksmith I just noticed the sim was running iOS 18.0 Beta, since I have Xcode 16 Beta installed on this machine, but that's Xcode 15.4, and the results are the same running on an iOS 17.5 sim.

@toddthomas Thanks Todd. The issue I see is in between persisting long enough and leaking: the objects can last much longer than expected, because they seem to be held by SwiftUI itself after the application code is done with them, for some internal reason.

cont’d…

@toddthomas In UIKit, if an instance variable in a UIViewController is the sole reference to an object, setting it to nil will release that object. I can use the Resource Acquisition is Initialization technique to free resources in a timely manner. That seems not to be the case with SwiftUI.

I’m not arguing it’s a bug, just that it’s (to me) unexpected behavior. A leaky abstraction maybe.