How to Make Deinit Take Effect in Swift

how to make deinit take effect in swift

class Car {
static var population: Int = 0
init() {
Car.population += 1

}
deinit {
Car.population -= 1
}
}

var cars: [Car] = [Car(), Car()]
print("Population:", Car.population) // "Population: 2"

// now the second car is removed from array and we have no other references to it
// it gets removed from memory and deinit is called
cars.removeLast()
print("Population:", Car.population) // "Population: 1"

However, the same can be achieved just by asking the number of items in cars array. And that's usually a better alternative than a private counter of instances.

To keep the items in memory you will always need some kind of register (e.g. an array) for them. And that register can keep them counted.

One possibility:

class CarPopulation {
var liveCars: [Car] = []
var junkCars: [Car] = []
}

Or you can keep them in one array and set junk on the car and count non-junk cars when needed:

class CarPopulation {
var cars: [Car] = []

func liveCars() -> Int {
return self.cars.filter { !$0.junk }.count
}
}

There are many possibilities but extracting the counters to some other class that owns the cars is probably a better solution.

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.

How to call deinit in Swift

The problem is that a playground is not real life. This is just one more reason for not using them (I think they are a terrible mistake on Apple's part). Use a real iOS app project and deinit will be called as expected.

Example from a real project:

class ViewController: UIViewController {
class Person{
let name:String;
init(name:String){
self.name = name;
println("\(name) is being initialized.");
}
deinit{
println("\(name) is being deInitialized.");

}
}
override func viewDidLoad() {
super.viewDidLoad()
var person:Person?;
person = Person(name:"leo");
person = nil;
}
}

That does what you expect it to do.

When is `deinit` exactly called? (in Swift)

The deinit is intended to release resources (such as freeing memory that is not under ARC).

(Thanks to Martin
and Rob's input, we can conclude below)

When is deinit called?

Usually, when last strong-reference gets out of scope,
deinit is called instantly (then deallocation happens).

But:

  1. If affected by autorelease feature (which has conditions), deinit is called significantly later,
    long after last reference gets out of scope (when autorelease pool is drained).
  2. Or when App is terminating, deinit is guaranteed to never get called!?
    (if deinit was not already called).
  3. Also in extremely common cases, deinit is called before strong-ref-variable's scope ends:
    • In Swift unlike other languages, when we set a weak-reference equal to a strong-reference,
      it could result to nil (which is absolutely allowed by Swift).

    • This happens if compiler detects that the remaining lines of scope,
      have NOT any strong-reference.

    • Possible workaround is using withExtendedLifetime(_:_:) global-method / function, like:

    withExtendedLifetime(myStrongRefVariable) {
    // Do something that only needs non-nil weak reference.
    }

Is it like C++ destructor?

There is no equivalent of a C++ destructor in ObjC or Swift.

(Objective-C++ object's destructor (dealloc) are called during program termination,
because that is required by C++ spec,
but that's all and else Obj-C++'s dealloc behavior is same as deinit.)

Is Swift using Garbage-Collector?

No, but whenever autorelease feature affects objects,
the deinit can be postponed (till autorelease-pool is drained, as mentioned above).

Deinit is it a good practice to implement it on viewControllers?

By default, you don't have to implement the deinit method in your classes:

Swift automatically deallocates your instances when they are no longer
needed, to free up resources. Swift handles the memory management of
instances through automatic reference counting (ARC), as described in
Automatic Reference Counting. Typically you don’t need to perform
manual cleanup when your instances are deallocated. However, when you
are working with your own resources, you might need to perform some
additional cleanup yourself. For example, if you create a custom class
to open a file and write some data to it, you might need to close the
file before the class instance is deallocated.

Swift Deinitialization Documentation - How Deinitialization Works Section.

Usually, when working with View Controllers it seems that there is no need to do such an implementation. However, as mentioned in @rmaddy's comment, it is still an approach for tracing memory leak or reference cycle with the view controller.

If your purpose is to check if the controller has been removed from the hierarchy (view controller life cycle), you could implement viewWillDisappear(_:) or viewDidDisappear(_:) methods; Note that calling on of these methods does not guarantees that the deinit will be called, i.e it does not mean that disappearing the view controller always leads to deallocate it (related: Deinit never called, explanation for deinit not called).

Also:

these Q&As should be useful:

  • When should I use deinit?

  • how to make deinit take effect in swift

  • Understand deinitialization and inheritance in swift language

Swift iOS -View Controller deinit runs when adding it to a new keyWindow

I got the answer from this reddit

An iOS application must have a rootViewController, create one and set
the keyWindow.rootViewController property to it. Then present your
view controller from that. Or just the rootViewController to be your
View Controller actually.

The reason the RedVC kept running it's deinit was because the keyWindow didn't have a rootViewController. I added the RedVC's view as a subview to the keyWindow keyWindow.addSubview(orangeVC.view) instead of making it it's rootVC:

keyWindow.rootViewController = redVC

Once I added it that the RedVC's deinit no longer ran when the animation occurred.

It should be noted that although it stopped the deinit from running I lost the animation and it also made the original keyWindow disappear. I should actually add this to a different UIWindow.

deinit not called in specific case

I expect deinit to be called at program termination

You should not expect that. Objects that exist at program termination are generally not deallocated. Memory cleanup is left to the operating system (which frees all of the program's memory). This is a long-existing optimization in Cocoa to speed up program termination.

deinit is intended only to release resources (such as freeing memory that is not under ARC). There is no equivalent of a C++ destructor in ObjC or Swift. (C++ and Objective-C++ objects are destroyed during program termination, since this is required by spec.)



Related Topics



Leave a reply



Submit