Deinitialization order is different when creating array differently

57 views Asked by At

TL;DR: Why, when the array is created together with initial values, the values are de-initialized right away, but filling the array values after the array is created produces different behavior?


I'm learning Swift after JavaScript, which has GC, so the concepts of ARC and de-initialization are somewhat novice for me. To understand it deeper, I've written the following code, based on the example from the official documentation:

// definitions; see the next two snippets for usage
class Bank {
    static var coins: Int = 100 {
        didSet {
            let diff = coins - oldValue
            let sign = diff < 0 ? "-" : "+"

            print("Bank: \(oldValue) \(sign) \(diff.magnitude) = \(coins)")
        }
    }

    static func give(_ amount: Int, to user: User) {
        guard amount <= coins else {
            print("[ERROR] The bank does not have enough coins; requested \(amount - coins) more coins than available")
            return
        }

        user.purse += amount
        coins -= amount
    }

    static func take(_ amount: Int, from user: User) {
        user.purse -= amount
        coins += amount
    }
}

class User {
    private static var count = 0

    let id: Int

    var purse = 0

    init() {
        User.count += 1

        id = User.count
    }

    deinit {
        print("User #\(id) perished")

        Bank.take(purse, from: self)
    }
}

When I create array with existing user instances (array = [value, value]), the de-initialization happens right after the assignment to nil (array[0] = nil):

var users: [User?] = [
    User(),
    User(),
]

Bank.give(90, to: users[0]!)
users[0] = nil
Bank.give(50, to: users[1]!) // works

//  Bank: 100 - 90 = 10
//  User #1 perished
//  Bank: 10 + 90 = 100
//  Bank: 100 - 50 = 50

… but when I first create an empty array and then populate it with values (array = []; array[0] = value; array[1] = value), de-initialization happens at some random point later:

var users = [User?](repeating: nil, count: 2)

users[0] = User()
users[1] = User()

Bank.give(90, to: users[0]!)
users[0] = nil
Bank.give(50, to: users[1]!) // fails

//  Bank: 100 - 90 = 10
//  [ERROR] The bank does not have enough coins; requested 40 more coins than available
//  User #1 perished
//  Bank: 10 + 90 = 100

What is the difference from the compiler point of view? And what is the rationale for this difference?

1

There are 1 answers

2
CRD On BEST ANSWER

TL;DR Do not rely on deallocation/de-initialization timing.

Figuring out exactly when deallocation, and hence de-initialization which occurs immediately prior, happens is a non-trivial process.

Threading (and just about every app is multi-threaded even if it doesn't directly use threading), compiler temporaries, passing values around, etc. all contribute to obfuscating exactly when the last reference to an object disappears.

On top of this the Swift Playground itself may keep non-obvious references to objects due to the way it works, which is what appears to be happening here. Putting your code into a Swift command line app works for us, but YMMV!

HTH