Why Don't Structs Have Deinitializers in Swift Like Classes

Why don't structs have deinitializers in Swift like classes?

deinit is for reference types (see https://stackoverflow.com/a/27366050/341994 for what that means) where the object pointed to persists independently until a reference count drops to zero.

A struct is a value type and does not require memory management that would need a deinit. Structs are not independently persistent the way classes are. Merely setting a property of a struct destroys it and replaces it. Assigning a struct copies it. Structs are created and destroyed in a highly lightweight way. They don’t need to signal their destruction; they are too lightweight for that.

Should an Instance of a Swift Class be assigned to `nil` to see the deinitializer in action?

The deinitialization is managed via the swift ARC. To simplify: it is triggered for an object (not a variable) when there is no longer a valid reference to the object. This happens for example when setting an optional variable to nil. But also by assigning another object to a non-optional variable. And also when variables are no longer in scope (e.g. exiting a function when the object is not returned).

The following adaptation of your code shows these different cases (see the comments for the details):

class A
{
var value: Int
init(_ value: Int)
{
self.value = value
print("A object with ivalue \(value) was initialized")
}
deinit
{
print("A object with value \(value) was deinitialized")
}
}

func test() {
var a: A = A(5) // (5) is initialized
let b = A(6) // (6) is initialized
a = b // no reference anymore to (5) so it is deinitialized
//a = nil // not allowed because it's not optional

var aOpt: A? = A(10) // (10) in intialized
aOpt = nil // no reference anymore to (10) so it is deinitialized
} // a dies, so no reference anymore to (6) which is deinitialized
test()
print ("This is the end")

I managed to get all the objects to be deinitialized before reaching the final print, simply by declaring them in a function body. This gives a tighter control on the variable lifecycles. But when you declare global or static variables, it's different, because the variables continue to exist until termination of a programme. Would my snippet run the test() outside of the function, the object (6) would not be deinitialized, since it would still be still referenced by a variable.

And with Swift, once the programme is terminated, it's over. Unlike C++, there is no guarantee that the remaining objects are properly deinitialized: Swift gives back the the resources to the the system without individually deinitializing every object.

If this is a problem for you, the best is to avoid global variables as much as possible (this is in any case a good practice). And if you have to use them, make sure the the app has a single exit point that cleans the object properly.

Why deinit is not being called on ObservableObject subclass?

struct is value type where as class is reference type (See Is Swift Pass By Value or Pass By Reference to know what this means).

So basically you don't make reference of a ContentView object and it's objects doesn't gets passed around. It rather makes copies of itself. So only way for deinit to be called is when all the copies of struct or ContentView goes out of scope.

When should I use deinit?

It's not required that you implement that method, but you can use it if you need to do some action or cleanup before deallocating the object.

The Apple docs include an example:

struct Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
coinsInBank += coins
}
}

class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}

So whenever the player is removed from the game, its coins are returned to the bank.

Swift: To struct, or not to struct

If the concern was merely how to implement the protocol's properties, I wouldn't necessarily let that sway my choice between struct vs class. If you had a variety of properties that your struct types must implement, you have two basic options:

  1. If you're talking about a few properties, just implement those few properties in your struct types that conform to that protocol. We do this all the time. E.g. when defining custom types that conform to MKAnnotation, we simply implement the three required properties.

    Sure, if we're talking about a much larger set of properties, this gets tedious, but the compiler holds our hand through this process, ensuring that we don't miss anything. So the challenge is fairly modest.

  2. While I'm not a fan of this approach, https://stackoverflow.com/a/38885813/1271826 shows that you could implement the shared properties as a component, where you have struct to wrap all of these properties, and then implement default computed properties for your protocol in an extension:

    enum SubviewArrangement {
    case none
    case horizontal
    case vertical
    case flow
    }

    struct ViewComponent {
    var frame = CGRect.zero
    var weight = 1
    var subviews = [ViewModel]()
    var subviewArrangement = SubviewArrangement.none
    }

    protocol HasViewComponent {
    var viewComponent: ViewComponent { get set }
    }

    protocol ViewModel: HasViewComponent { }

    extension ViewModel {
    var frame: CGRect {
    get { return viewComponent.frame }
    set { viewComponent.frame = newValue }
    }
    var weight: Int {
    get { return viewComponent.weight }
    set { viewComponent.weight = newValue }
    }
    var subviews: [ViewModel] {
    get { return viewComponent.subviews }
    set { viewComponent.subviews = newValue }
    }
    var subviewArrangement: SubviewArrangement {
    get { return viewComponent.subviewArrangement }
    set { viewComponent.subviewArrangement = newValue }
    }
    }

    Where, you can then create an instance that conforms to ViewModel, like so:

    struct LabelModel: ViewModel {
    var viewComponent = ViewComponent()
    }

    var label = LabelModel()
    label.weight = 2
    print(label.weight)

    I have to confess, this isn't the most elegant approach. (I hesitate to even present it.) But it avoids having to implement all of those properties individually in your types that conform to ViewModel.

So, let's set the property question aside. The real question is whether you should be using value type (struct) or reference type (class). I think it's illuminating to consider Apple's discussion of value vs reference semantics near the end (@42:15) the Protocol-Oriented Programming in Swift video. They touch upon those cases where you actually may still want to use classes. For example, they suggest you might want to use reference types when, "Copying or comparing instances doesn't make sense". They suggest this rule might apply when dealing with "Window" instances. The same applies here.

On top of that, it doesn't seem to me that there is much benefit to use value types to represent a view hierarchy, which is a collection of reference type objects. It only makes it more confusing. I would just stick with class types, as it will accurately mirror the view hierarchy it represents.

Don't get me wrong: We're so used to using reference types that I think it's always good to challenge our preconceived notions and take a long hard look at whether a value type could better address the situation. In this case, though, I simply wouldn't worry about it and just stick with a class hierarchy that mirrors the hierarchy of those objects you're modeling.


That having been said, the class hierarchy proposed in your question doesn't quite feel right, either. It feels strange that you can actually instantiate a ViewModel to which you can't later add subviews (whereas all UIView objects have subview property). Also, your horizontal and vertical group types don't feel correct either. For example, should it be a single type with some "axis" property, like UIStackView or some other "arrangement" property, to broaden the notion to capture UICollectionView layouts, too?. As you'll see in my ViewComponent example, above, I've flattened this a bit, with these two caveats in mind, but do whatever you see fit.

difference between NSObject and Struct

Classes and structs in Swift are much closer than in many languages. Both can have properties, method, initializers, subscripts, can conform to protocols, and can be extended. However, only classes can take advantage of inheritance and use deinitializers, and because classes are used by reference, you can have more than one reference to a particular instance.

Structs are used throughout Swift -- arrays, dictionaries, everything optional, and more are built on the struct type, so performance should be very high. You can use struct whenever you don't need the inheritance or multiple references that classes provide.



Related Topics



Leave a reply



Submit