(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
