(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とは何か
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 }
}
- プロパティ
objectWillChange
はSelf.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でも特に気にしない
- 使用されるオブジェクトのプロパティは全て読みだされるので
- SwiftUIのViewとしては更新されたら
-
- どのような場合にカスタマイズする?
- Outputを変更したい場合がある?
- 思いつかないな...
- Outputを変更したい場合がある?
必ずしも@Published
を使う必要はない
@Published
を使いたいのは次の優先度のとき
- Viewが
Binding
なオブジェクトを取り出してバインディングしたい(読み込みと書き込み) -
ObservableObject
の中でfilter
やflatMap
したい -
objectWillChange.send()
を自動でやりたい
RxSwiftのようなUIからストリームを作れる仕組みがないために、Binding
での値のやり取りが楽なので@Published
を使いたい。本当はそれ一択かもしれない。
SwiftUIガイドブック
BOOTHで電子書籍として販売しています。
読者のターゲットは次のような人を想定しています
- SwiftUIのレンダリングシステムが自動で更新されるタイミングを知らない
- 更新のきっかけになる
$ObservedObject
の@Published
にもやもやしている - SwiftUIのViewが自動で更新されるタイミングやその範囲にもやもやしている
- 更新のきっかけになる
-
@State
や@Binding
、@ObservedObject
の違いがよくわからない-
@Environment
と@EnvironmentObject
の違いもまだ知らない
-
- $オペレータを雰囲気で使って雰囲気で修正している
- コンパイルエラーになるたびに修正している
- propertyWrapperとdynamicMemberLookupを理解して使っていない
SwiftUIでpropertyWrapperとdynamicMemberLookupがあやふやだなーという気持ちになったままコードを書くことも多いのではないかと思ってます。
SwiftUI触ってる人に質問ですけどpropertyWrapperとdynamicMemberLookupについて理解してSwiftUIを触ってる?それともあやふやなままコンパイラに都度注意されてる?
— imajô (@yimajo) February 9, 2020