(at)PublishedがどのようにSwiftUIのViewを更新するのか

# (at)PublishedがどのようにSwiftUIのViewを更新するのか

`@Published`属性がどのようにSwiftUIのViewを更新するのかについて書いておきます。

WWDC19の動画を見るとそもそも`@Published`属性がまだ存在していないのと、その当時は`ObservableObject`型が`BindableObject`型であったり、`@ObservedObject`属性が`@ObjectBinding`属性だったりするのでややこしいためです。

私の結論としては`@Published`あると便利なんだけど、便利すぎるし内部が想像しづらいので基礎を知らないと無駄にSwiftUI.Viewを更新させることになると思います。さらに、コードを書き換えてるときや他人のコードで意味不明に`@Published`されたコードを見るかもしれません。そういうときに知っておきたいことを書いています。

## `@ObservedObject`属性として指定されたObservableObjectはSwiftUIにsubscribeされている

- Viewは`@ObservedObject`指定した`ObservableObject`の`objectWillChange`をsubscribeしている
  - subscribeし発火されるとViewを更新するようにしている
  - `ObservableObject`で`objectWillChange`を使い発火させればいい
- `@Published`はプロパティのwillSetで`objectWillChange`を使い発火させるのを暗黙的に行っている
 

![objectwillchange_subscribe_from_swiftui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16400/16fd3c58-ea7a-59e6-bb7f-fe92f2d1f15b.png)

## objectWillChangeとは何か

`objectWillChange`はCombineで定義されている`ObservableObject`を見るとわかります。

```swift
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ObservableObject : AnyObject {

    /// The type of publisher that emits before the object has changed.
    associatedtype ObjectWillChangePublisher : Publisher  // 制約の追加 Swift4.1（SE-0157） 
       = ObservableObjectPublisher  // デフォルトで利用する型
         where Self.ObjectWillChangePublisher.Failure == Never // 条件

    /// A publisher that emits before the object has changed.
    var objectWillChange: Self.ObjectWillChangePublisher { get }
}
```

- プロパティ`objectWillChange`は`Self.ObjectWillChangePublisher`を返す
- associatedtypeの`ObjectWillChangePublisher`
  - `Publisher`プロトコルを制約として追加している
    - 言い換えると`ObservableObject`はパブリッシャーを保持している
    - デフォルトでは`ObservableObjectPublisher`クラスを使う
      - さらにエラーは流さない
      - おそらくカスタマイズした`Publisher`も使える


## ObservableObjectPublisherとは何か

Combineで用意された`ObservableObjectPublisher`クラスを確認してみます。


```swift
/// The default publisher of an `ObservableObject`.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final public class ObservableObjectPublisher : Publisher {

    /// The kind of values published by this publisher.
    public typealias Output = Void

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    public typealias Failure = Never

    public init()

    /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
    ///
    /// - SeeAlso: `subscribe(_:)`
    /// - Parameters:
    ///     - subscriber: The subscriber to attach to this `Publisher`.
    ///                   once attached it can begin to receive values.
    final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output

    final public func send()
}
```

- OutputはVoidなのでイベントは `(Void) -> Void`のクロージャ
    - `.sink { (Void) -> Void in  ... }`
        - SwiftUIのViewとしては更新されたら`body`が実行される
            - 使用されるオブジェクトのプロパティは全て読みだされるので
                - イベント時のOutputがVoidでも特に気にしない
- どのような場合にカスタマイズする？
    - Outputを変更したい場合がある?
        - 思いつかないな...

## 必ずしも`@Published`を使う必要はない

`@Published`を使いたいのは次の優先度のとき

- Viewが`Binding`なオブジェクトを取り出してバインディングしたい（読み込みと書き込み）
- `ObservableObject`の中で`filter`や`flatMap`したい
- `objectWillChange.send()`を自動でやりたい

RxSwiftのようなUIからストリームを作れる仕組みがないために、`Binding`での値のやり取りが楽なので`@Published`を使いたい。本当はそれ一択かもしれない。

# SwiftUIガイドブック

BOOTHで電子書籍として販売しています。

https://booth.pm/ja/items/1829015

読者のターゲットは次のような人を想定しています

- SwiftUIのレンダリングシステムが自動で更新されるタイミングを知らない
  - 更新のきっかけになる`$ObservedObject`の`@Published`にもやもやしている
  - SwiftUIのViewが自動で更新されるタイミングやその範囲にもやもやしている
- `@State`や`@Binding`、`@ObservedObject`の違いがよくわからない
  - `@Environment`と`@EnvironmentObject`の違いもまだ知らない
- $オペレータを雰囲気で使って雰囲気で修正している
  - コンパイルエラーになるたびに修正している
  - propertyWrapperとdynamicMemberLookupを理解して使っていない

SwiftUIでpropertyWrapperとdynamicMemberLookupがあやふやだなーという気持ちになったままコードを書くことも多いのではないかと思ってます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">SwiftUI触ってる人に質問ですけどpropertyWrapperとdynamicMemberLookupについて理解してSwiftUIを触ってる？それともあやふやなままコンパイラに都度注意されてる？</p>&mdash; imajô (@yimajo) <a href="https://twitter.com/yimajo/status/1226549034216611840?ref_src=twsrc%5Etfw">February 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>