🕊
[Swift] Class と Struct の変数の didSet 挙動の違いについて
結論
-
Class
やStruct
を持つ変数について、didSet
を実装する時は、挙動が異なるので注意が必要
共通コード
以下のような、適当な Class
と Struct
を要します。
// 🏫 Class
class Moge {
var mogemoge: Int = 0 {
didSet {
print("mogemoge: \(mogemoge)")
}
}
init() {}
func update(mogemoge: Int) {
self.mogemoge = mogemoge
}
}
// 🏗 Struct
struct Fuga {
var fugafuga: Int = 0 {
didSet {
print("fugafuga: \(fugafuga)")
}
}
// struct の場合 `mutating` をつけないとコンパイルエラー⚠️
mutating func update(fugafuga: Int) {
self.fugafuga = fugafuga
}
}
上記の Class
または Struct
を変数にもつ Class
をさらに用意します。
class Hoge {
// 🔢 Int の場合
var hogehoge: Int = 0 {
didSet {
print("hogehoge: \(hogehoge)")
}
}
// 🏫 Class の場合
var moge: Moge = .init() {
didSet {
print("moge: \(moge), mogemoge: \(moge.mogemoge)")
}
}
// 🏗 Struct の場合
var fuga: Fuga = .init() {
didSet {
print("fuga: \(fuga), fugafuga: \(fuga.fugafuga)")
}
}
init() {}
}
let hoge: Hoge = .init()
ここで、この hoge
に対して、値の更新を走らせるとどうなるか実験してみます。
🔢 Int の場合
🔢 Int の場合
hoge.hogehoge = 1
// hogehoge: 1
当たり前の挙動になりました。
🏫 Class の場合
🏫 Class の場合
hoge.moge = Moge()
// moge: __lldb_expr_49.Moge, mogemoge: 0
hoge.moge.mogemoge = 2
// mogemoge: 2
hoge.moge.update(mogemoge: 3)
// mogemoge: 3
これもなんとなく想像の通りの結果だと思います。
🏗 Struct の場合
🏗 Struct の場合
hoge.fuga = Fuga()
// fuga: Fuga(fugafuga: 0), fugafuga: 0
hoge.fuga.fugafuga = 4
// fugafuga: 4
// fuga: Fuga(fugafuga: 4), fugafuga: 4 ← ❗️❗️
hoge.fuga.update(fugafuga: 5)
// fugafuga: 5
// fuga: Fuga(fugafuga: 5), fugafuga: 5 ← ❗️❗️
Struct
の持っている変数の変更を実施した場合、Struct
の場合は、Class
と違って、変更された変数の didSet
に加えて、さらに、hoge
が持っている fuga
の Struct
自身の didSet
も走っています。
このように Class
や Struct
を持つ変数について、didSet
を実装する時は、挙動が異なるので注意が必要です。
考察
このような挙動の差は、Class
が参照型であり、Struct
が値型であること原因であると考えられます。
また、似たような話として、@Published
や CurrentValueSubject
の挙動も Class
と Struct
で差があります。
以上になります。
Discussion