【Swift】Combineの@PublishedとReferenceWritableKeyPathの関係

# 背景
Combineの`@Published`を利用して値の監視を行う際に生成されるReferenceWritableKeyPathがViewControllerのdeinitによって破棄された後もメモリ内に残っていたのを見つけました。
気になったため調べてみました。

自分自身ダイナミックライブラリやメモリ周り・Xcodeの仕様などを詳細に知っている訳ではありません。間違っているなどありましたらコメントいただけるとありがたいです。

# 結果
・.assign(to:)を利用することでReferenceWritableKeyPathを生成せずに値の更新ができる。
・`@Published`はwrappedValueを利用した値の変更やSwiftUIの利用をする場合はReferenceWritableKeyPathの機能を利用して監視を実現していそう。
・ReferenceWritableKeyPathはlibswiftCore.dylibのダイナミックライブラリ上で管理される。

# 検証
検証項目は以下です。
・`@Published`のwrappedValueに値を代入する。
・`@Published`のprojectedValueを通して値を変更する。
・CurrentValueSubjectを利用した場合。
・SwiftUIで`@Published`のwrappedValueに値を代入する。
・SwiftUIで`@Published`のprojectedValueを通して値を変更する。

対象のプロパティが定義されたクラスの破棄は、ViewControllerからpresentさせた先のViewControllerをdeinitさせることで実現させます。

### 検証環境
・MacBook Pro (14インチ、2021) OS: macOS Monterey 12.6
・Xcode 14.2
・iOS 16.2 (iPhone 14 simulator)

## `@Published`のwrappedValueに値を代入する。
ReferenceWritableKeyPathが生成され、deinit後、そのReferenceWritableKeyPathは残る。

## `@Published`のprojectedValueを通して値を変更する。
assign(to:)を利用した。ReferenceWritableKeyPathがそもそも生成されない。

## CurrentValueSubjectを利用した場合。
ReferenceWritableKeyPathがそもそも生成されない。

## SwiftUIで`@Published`のwrappedValueに値を代入する。
ReferenceWritableKeyPathが生成され、deinit後、そのReferenceWritableKeyPathは残る。

## SwiftUIで`@Published`のprojectedValueを通して値を変更する。
ReferenceWritableKeyPathが生成され、deinit後、そのReferenceWritableKeyPathは残る。

SwiftUIでは値の変更関係なしに関連のReferenceWritableKeyPathが生成されていた。

# まとめ
・.assign(to:)を利用することでReferenceWritableKeyPathを生成せずに値の更新ができる。
・`@Published`はwrappedValueを利用した値の変更やSwiftUIの利用をする場合はReferenceWritableKeyPathの機能を利用して監視を実現していそう。

そして検証する中でReferenceWritableKeyPathはlibswiftCore.dylibのダイナミックライブラリ上で管理されることがわかりました。

libswiftCore.dylib内にReferenceWritableKeyPathがなぜ保持されるのかまではわかりませんでしたがドキュメントには以下のような記載がありました。

> A ReferenceWritableKeyPath may have a reference prefix of read-only components that can be projected before initiating mutation

ReferenceWritableKeyPath は、変異を開始する前に投影できる読み取り専用コンポーネントの参照プレフィックスを持つことができる。

> If the key path has a reference prefix, then exactly one component must have the end of reference prefix bit set in its component header. This indicates that the component after the end of the reference prefix will initiate mutation.

キーパスに参照プレフィックスがある場合、ちょうど1つのコンポーネントが、そのコンポーネントヘッダに参照プレフィックス終了ビットをセットしていなければならない。これは、参照プレフィックスの終わりの後のコンポーネントが変異を開始することを示します。

ReferenceWritableKeyPathの場合ReferenceWritableKeyPathであることを示す専用のprefixを持つことができ、そのprefixがあるときは必ず終了しているか・していないかをセットする必要があるため、今回その値の変動を見ることはできませんでしたが仕組み上残っていても問題は無いように感じました。

検証に使ったプロジェクトはこちらです。
https://github.com/kokiTakashiki/ReferenceWritableKeyPathResearch/tree/main/ReferenceWritableKeyPathResearch

最後までお読みいただきありがとうございました。

# 参考

Can ＠Published be implemented in Swift?
https://forums.swift.org/t/can-published-be-implemented-in-swift/59057/1

How is the `Published` property wrapper implemented?
https://forums.swift.org/t/how-is-the-published-property-wrapper-implemented/58223/1

Key Path Memory Layout
https://github.com/apple/swift/blob/main/docs/ABI/KeyPaths.md

Using Dynamic Libraries
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/UsingDynamicLibraries.html