Help us understand the problem. What is going on with this article?

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

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

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

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

私の結論としては@Publishedあると便利なんだけど、コードを書き換えてるときになぜこのプロパティに@Publishedにしたんだろう、という感覚になりやすいので動いたら@Publishedを避けてみる。これはおとなしく手動でやっといたら意図も読みやすくて混乱しなかったのに、という気持ちになりやすいからです。

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

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

objectwillchange_subscribe_from_swiftui.png

objectWillChangeとは何か

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

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

ObservableObjectPublisherとは何か

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

/// 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の中でfilterflatMapしたい
  • 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があやふやだなーという気持ちになったままコードを書くことも多いのではないかと思ってます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした