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?
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