[Swift] [Combine] @Published な変数が Class と Struct では挙動が異なる件


# 結論

`@Published` な変数、または、 `CurrentValueSubject` の `Output` に、`Struct` または `Class` を設定した場合に以下のような差が生じる。

- `Struct` の場合
  - 値を更新した場合、**出力が走る**
- `Class` の場合
  - 値をを更新した場合、**出力は走らない**

# Struct の場合

```swift
// 🌟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` でも、出力がながれるので注意が必要です。

- [[Swift] [Combine] CurrentValueSubject で Struct 型かつ nil を許容したときの注意点](https://zenn.dev/ikuraikura/articles/2022-02-26-cvs)

# Class の場合

```swift
// 🌟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` となる変数を更新した場合、**出力は走らない**

# 共通コード

```swift
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` の場合

```Swift
class Hoge {
    @Published var moge: Moge = .init(mogemoge: 0)
    @Published var fuga: Fuga = .init(fugafuga: 0)
    
    init() {}
}

let hoge: Hoge = .init()
```

## `Struct` の場合

```Swift
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` の場合

```Swift
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` の場合

```Swift
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` の場合

```Swift
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` の場合

```Swift
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` で差があります。

- 参考
  - [値型と参照型 - Swiftの始め方](https://swift.codelly.dev/guide/%E6%A7%8B%E9%80%A0%E4%BD%93%E3%81%A8%E3%82%AF%E3%83%A9%E3%82%B9/%E5%80%A4%E5%9E%8B%E3%81%A8%E5%8F%82%E7%85%A7%E5%9E%8B.html)
  - [[Swift] Class と Struct の変数の didSet 挙動の違いについて](https://zenn.dev/ikuraikura/articles/2022-02-27-did-set)

# まとめ

`@Published` な変数、または、 `CurrentValueSubject` の `Output` に、`Struct` または `Class` を設定した場合に以下のような差が生じる。

- `Struct` の場合
  - 値を更新した場合、**出力が走る**
- `Class` の場合
  - 値をを更新した場合、**出力は走らない**

以上になります。