🌾
[Swift] [Combine] @Published な変数が Class と Struct では挙動が異なる件
結論
@Published
な変数、または、 CurrentValueSubject
の Output
に、Struct
または Class
を設定した場合に以下のような差が生じる。
-
Struct
の場合- 値を更新した場合、出力が走る
-
Class
の場合- 値をを更新した場合、出力は走らない
Struct の場合
// 🌟struct🌟
struct Hoge {
var mogemoge: Int
init(mogemoge: Int) {
self.mogemoge = mogemoge
}
}
let hogePub: CurrentValueSubject<Hoge, Never> = .init(Hoge(mogemoge: 0))
hogePub
.dropFirst()
.sink { print("receive: \(String(describing: $0.mogemoge))") }
.store(in: &cancellables)
hogePub.value = Hoge(mogemoge: 1)
hogePub.value.mogemoge = 2 // ← 注目🌟
hogePub.value = Hoge(mogemoge: 3)
// (出力)
// receive: 1
// receive: 2 ← 注目🌟
// receive: 3
-
var
となる変数を更新した場合、出力が走る
ちなみに nil
を許容している場合は、.value
自体が nil
でも、出力がながれるので注意が必要です。
Class の場合
// 🌟class🌟
class Hoge {
var mogemoge: Int
init(mogemoge: Int) {
self.mogemoge = mogemoge
}
}
let hogePub: CurrentValueSubject<Hoge, Never> = .init(Hoge(mogemoge: 0))
hogePub
.dropFirst()
.sink { print("receive: \(String(describing: $0.mogemoge))") }
.store(in: &cancellables)
hogePub.value = Hoge(mogemoge: 1)
hogePub.value.mogemoge = 2 // ← 注目🌟
hogePub.value = Hoge(mogemoge: 3)
// (出力)
// receive: 1
// receive: 3
↑
receive: 2 がない!!
-
var
となる変数を更新した場合、出力は走らない
共通コード
import Combine
var cancellables = Set<AnyCancellable>()
struct Moge {
var mogemoge: Int = 0
init(mogemoge: Int) {
self.mogemoge = mogemoge
}
}
class Fuga {
var fugafuga: Int = 0
init(fugafuga: Int) {
self.fugafuga = fugafuga
}
}
@Published
の場合
class Hoge {
@Published var moge: Moge = .init(mogemoge: 0)
@Published var fuga: Fuga = .init(fugafuga: 0)
init() {}
}
let hoge: Hoge = .init()
Struct
の場合
hoge.$moge
.dropFirst()
.sink { print("mogemoge: \($0.mogemoge)") }
.store(in: &cancellables)
hoge.moge = Moge(mogemoge: 1)
hoge.moge.mogemoge = 2 // ← 注目🌟
hoge.moge = Moge(mogemoge: 3)
// (出力)
// mogemoge: 1
// mogemoge: 2 ← 注目🌟
// mogemoge: 3
-
Struct
の場合- 値を更新した場合、出力が走る
Class
の場合
hoge.$fuga
.dropFirst()
.sink { print("fugafuga: \($0.fugafuga)") }
.store(in: &cancellables)
hoge.fuga = Fuga(fugafuga: 4)
hoge.fuga.fugafuga = 5 // ← 注目🌟
hoge.fuga = Fuga(fugafuga: 6)
// (出力)
// fugafuga: 4
// fugafuga: 6
↑
receive: 5 がない!!
-
Class
の場合- 値をを更新した場合、出力は走らない
CurrentValueSubject
の場合
class Hoge {
var mogePub: CurrentValueSubject<Moge, Never> = .init(.init(mogemoge: 0))
var fugaPub: CurrentValueSubject<Fuga, Never> = .init(.init(fugafuga: 0))
init() {}
}
let hoge: Hoge = .init()
Struct
の場合
hoge.mogePub
.dropFirst()
.sink { print("mogemoge: \($0.mogemoge)") }
.store(in: &cancellables)
hoge.mogePub.value = Moge(mogemoge: 1)
hoge.mogePub.value.mogemoge = 2 // ← 注目🌟
hoge.mogePub.value = Moge(mogemoge: 3)
// (出力)
// mogemoge: 1
// mogemoge: 2 ← 注目🌟
// mogemoge: 3
-
Struct
の場合- 値を更新した場合、出力が走る
Class
の場合
hoge.fugaPub
.dropFirst()
.sink { print("fugafuga: \($0.fugafuga)") }
.store(in: &cancellables)
hoge.fugaPub.value = Fuga(fugafuga: 4)
hoge.fugaPub.value.fugafuga = 5 // ← 注目🌟
hoge.fugaPub.value = Fuga(fugafuga: 6)
// (出力)
// fugafuga: 4
// fugafuga: 6
↑
receive: 5 がない!!
-
Class
の場合- 値をを更新した場合、出力は走らない
考察
このような挙動の差は、Class
が参照型であり、Struct
が値型であること原因であると考えられます。
また、似たような話として、didSet
の挙動も Class
と Struct
で差があります。
まとめ
@Published
な変数、または、 CurrentValueSubject
の Output
に、Struct
または Class
を設定した場合に以下のような差が生じる。
-
Struct
の場合- 値を更新した場合、出力が走る
-
Class
の場合- 値をを更新した場合、出力は走らない
以上になります。
Discussion