【Swift】deinitではwillSet/didSetが呼ばれないというお話


結論

init/deinit内でプロパティに代入を行っても、willSet/didSetは呼ばれないので注意しましょう。
ただし、スーパークラスのプロパティのwillSet/didSetは呼ばれるようです。

公式ガイドの注意書き

公式ガイド(The Swift Programming LanguageのProperties)に、以下のような注意書きがあります。

NOTE
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.1

注意
サブクラスのイニシャライザ内で、スーパークラスのイニシャライザが呼ばれた後に、プロパティがセットされたとき、スーパークラスのプロパティのwillSetとdidSetのオブザーバーが呼ばれます。スーパークラスのイニシャライザが呼ばれる以前に、自身のプロパティをセットしている間は呼ばれません。2

なるほど。スーパークラスのプロパティのdidSet/willSetについては、サブクラス内のイニシャライザでスーパクラスのイニシャライザを呼んだ後であれば、機能するということらしいです。
クラス自身で定義したプロパティのdidSet/willSetについては、イニシャライザで呼ばれないのは当然だろということか、記述が見当たりませんでした。

コードで確認

確かにスーパークラスのプロパティのdidSetについては呼ばれました。

class Animal {
    var age: Int {
        didSet {
            print(#function, age, oldValue)
        }
    }

    init(age: Int) {
        self.age = age // -> 呼ばれない
    }
}

class Tiger: Animal {
    var family: String {
        didSet {
            print(#function, family, oldValue)
        }
    }

    init(age: Int, family: String) {
        self.family = family  // -> 呼ばれない
        super.init(age: age) // -> 呼ばれない

        self.age += 0  // -> didSetが呼ばれる!!
        self.family += "" // -> 呼ばれない
    }

    deinit {
        print(#function)
        age = 0 // -> 呼ばれる
        family = "" // -> 呼ばれない
    }
}

どういうときに注意すべき?

例えば、Timerをdeinitでinvalidateしたいときに、以下のような書き方をすると呼ばれないので注意しましょう。

class ViewController2: UIViewController {
    var timer: Timer? {
        willSet {
            timer?.invalidate()
        }
    }

    deinit {
        timer = nil // nilを代入してもwillSetは呼ばれない!
        // -> timer?.invalidate()
    }
    ...
}

参考

Swiftでdeinit時にメンバ変数(property)のdidSetが呼ばれない気がした
Can I use didSet in deinit?


  1. [The Swift Programming Language - Properties](https://docs.swift.org/swift-book/LanguageGuide/Properties 

  2. 太字による強調は訳者による