Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
SwiftUI: 更新検知と値の生存期間
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Iceman
June 27, 2019
Programming
1.2k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SwiftUI: 更新検知と値の生存期間
Iceman
June 27, 2019
More Decks by Iceman
See All by Iceman
わいわいswift#39 Swiftの型をTypeScriptで表す
sidepelican
0
360
わいわいswiftc#35夢が広がる!コード生成でどこでもSwift
sidepelican
0
490
元ゲーム開発者が贈る描画パフォーマンス改善 / Rendering performance improvement from a game developer
sidepelican
4
1.8k
わいわいswiftc#19Genericsの特殊化
sidepelican
0
490
わいわいswiftc#17Genericsの特殊化
sidepelican
0
110
クックパッドiOSアプリのパフォーマンス改善
sidepelican
0
820
DispatchQueue.syncが動作するスレッド
sidepelican
0
400
Other Decks in Programming
See All in Programming
「AI時代、配布するPythonコードをどう守るか: 難読化の実験と判断軸」 #PyconJP2026
pkshadeck
PRO
2
160
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
440
MIZARU@SPAJAM2026 第二回予選
1901drama
0
100
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
830
不幸な GC
chencmd
0
880
承認済みなのに差戻しできてしまうバグ、型で潰せます
shinchit
0
140
自分的「カンファレンスの楽しみ方」
syumai
0
190
Hono + Inertia + React で LP を構築した話
oukayuka
2
210
業務時間外もAIに働いてもらう話
colorful12
3
9.9k
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.7k
Laravelのアプリケーションをどこにデプロイするか #ツナギメオフライン.9
akase244
0
110
新人はどこまで自力でやり、どこからAIに頼るべきか/エンジニア育成に向き合う_先輩たちの悩みと知見共有会
toppan_digital_dev
1
560
Featured
See All Featured
Speed Design
sergeychernyshev
33
2.1k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
490
sira's awesome portfolio website redesign presentation
elsirapls
0
400
ラッコキーワード サービス紹介資料
rakko
1
4.7M
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
570
Prompt Engineering for Job Search
mfonobong
0
440
Optimizing for Happiness
mojombo
378
71k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
270
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
220
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Designing Experiences People Love
moore
143
24k
Transcript
わいわいswiftc #12 SwiftUI: 更新検知と値の生存期間 @iceman5499
SwiftUIにはViewのプロパティの更新を検知するため の@propertyDelegateがたくさんある @State @Binding @ObjectBinding @EnvironmentObject @Environment (@GestureStateもあるけど今回は未調査)
任意の型を入れられるものと、 BindableObject を入れるものと2種 類がある 名前 中に入る型 @State T @Binding T
@ObjectBinding T: BindableObject @EnvironmentObject T: BindableObject @Environment T @Stateなどは任意の型を使用できるが、 struct・enumとclassとで挙動が異なる(後述) 順にみていく
@State
@State 値を保持できる。値が変化したとき値を使用したViewがリビルドされる struct StateBasic : View { @State var count:
Int = 0 var body: some View { VStack { Text("\(count)") Button(action: { // ボタンを押すと表示が更新 self.count += 1 }) { Text("inc") } } } }
@State 値は1つのNavigation単位で生存する Backで画面が消えると初期化される (Viewの構成によっては初期化されないパターンもある) Presentationされていた場合は画面が消えても値が残る (Viewの構成によっては初期化されるパターンもある)
@State View.body内で値が1度も取り出されなかった場合、値が更新され てもそのViewはリビルドされない
struct StateUnused : View { @State var okane: Int =
0 var body: some View { VStack { if AccountManager.shared.isPurchased { // 初回でここに入らなかった場合は // 以降okane が変化してもリビルドされない Text("\(okane)") } else { Text("not purchased") } Button(action: { AccountManager.shared.isPurchased = true self.okane += 1 }) { Text("purchase") } } } }
修正後 struct StateUnusedFix : View { @ObjectBinding var accountManager: AccountManager
@State var okane: Int = 0 var body: some View { VStack { if accountManager.isPurchased { Text("\(okane)") } else { Text("not purchased") } Button(action: { self.accountManager.isPurchased = true self.okane += 1 }) { Text("purchase") } } } } propertyDelegateで監視されていない値を使って分岐をしてはいけない
@State 中の値がclassのときは値の保持はされるが更新検知はされない が、 BindableObject に適合していれば検知される enumは更新検知される
@Binding
@Binding プロパティに対するエイリアスのようなもの 実体は持たないが値を取り出せるし値を変化させれば依存している各所 がリビルドされる
private struct IncButton: View { @Binding var count: Int var
body: some View { Button(action: { self.count += 1 }) { Text("inc") } } } struct BindingBasic : View { @State var count = 0 var body: some View { VStack { Text("\(count)") // ↓ 値への参照を渡して更新してもらう IncButton(count: $count) } } }
@Binding うまく使えばリビルド範囲を制御できるかもしれない? リビルド範囲を狭めることでパフォーマンスに恩恵があるかは不明 struct BindingSibling : View { @State var
count = 0 var body: some View { // count が変化してもこれ自体はリビルドされない VStack { CounterView(count: $count) // ← この中はリビルド IncButton(count: $count) } } }
@ObjectBinding
@ObjectBinding BindableObject に適合したオブジェクトは@ObjectBindingを使用す ることで更新検知できるようになる public protocol BindableObject : AnyObject, DynamicViewProperty,
Identifiable, _BindableObjectViewProperty { associatedtype PublisherType : Publisher where Self.PublisherType.Failure == Never var didChange: Self.PublisherType { get } }
使用例? それらしい使い方だが、これは罠にはまる class CountViewModel: BindableObject { let didChange = PassthroughSubject<CountViewModel,
Never>() var count: Int = 0 func inc() { count += 1 didChange.send(self) } } struct ObjectBindingBasic : View { @ObjectBinding var viewModel = CountViewModel() var body: some View { VStack { Text("\(viewModel.count)") Button(action: { self.viewModel.inc() }) { Text("inc") } } } }
@ObjectBinding 値の保持はされない Viewがリビルドされるたびに値が初期化される 前ページのコードの場合、ネストした画面で使用されて親 Viewがリビルドを発火したタイミングでcountが0に戻る それなのに更新検知対象は初回に生成されたオブジェクトのみ 初回生成されたやつはどこかに保持され一生触れない バグっぽい Navigation単位ではなぜか値が保持される 最も挙動が不安定
値が保持されない問題の回避策? viewModelを@Stateとして保持することでNavigation単位で生存 させられる struct ObjectBindingBasicFix : View { @State var
viewModel = CountViewModel() var body: some View { VStack { Text("\(viewModel.count)") Button(action: { self.viewModel.inc() }) { Text("inc") } } } } しかし@Stateにclassを保持させること自体は想定されていなさそ うなため、おとなしく親Viewが子ViewのviewModelを管理してあ げるか後述する@EnvironmentObjectを利用したほうがよさそう
@ObjectBinding Q. @Stateを使っても BindableObject の更新検知はできるので何に 使うのこれ? A. delegateValueとDMLのマジックで $viewState.count と書く
と Binding<Int> が取得できるためそこらへんで差別化できる
@EnvironmentObject
@EnvironmentObject 親や祖父やその先の祖先で宣言されたオブジェクトを一気にジャンプし て子Viewが取得できる仕組み Flutterでいう InheritedWidget に近い
@EnvironmentObject struct CounterPage: View { @EnvironmentObject var counter: CounterEnvironment var
body: some View { // counter が利用できる or 実行時クラッシュ ... } } struct EnvironmentObjectBasic : View { var body: some View { VStack { CounterPage() CounterPage() } .environmentObject(CounterEnvironment()) // ⭐ } } ⭐ の行がなければ実行時クラッシュする
画面単位でしか生存していないので、NavigationかPresentationを またぐとリセットされる struct EnvironmentObjectPush : View { var body: some
View { VStack { // どちらもボタンタップでクラッシュする NavigationButton(destination: CounterPage()) { Text("push") } PresentationButton(destination: CounterPage()) { Text("present") } } .environmentObject(CounterEnvironment()) } }
@Environment
@Environment EnvironmentObjectと似ていてこちらは値型を扱うといったイメージ。 EnvironmentObjectとは異なり .environment(..., ...) が宣言され ていなくてもデフォルト値が使用されるためクラッシュしない
@Environment まず EnvironmentValues に値を生やす extension EnvironmentValues { var counter: CounterEnvironment
{ get { self[CounterEnvironmentKey.self] } set { self[CounterEnvironmentKey.self] =newValue} } } struct CounterEnvironmentKey: EnvironmentKey { static var defaultValue: CounterEnvironment {.init()} } struct CounterEnvironment { var count: Int = 0 }
その後@Environmentと取り出したいプロパティのkeyPathをわた して取り出す struct CounterPage2: View { @Environment(\.counter) var counter: CounterEnvironment
var body: some View { VStack { Text("\(counter.count)") Button(action: { // Environment はget-only なため変更できない }) { Text("inc") } } } }
@Environment @EnvironmentObjectと同様に画面単位でしか生存していないの で、NavigationかPresentationをまたぐとデフォルト値が利用され る
生存期間まとめ 名前 生存期間 @State Navigation単位。Presentationでは値が残る @Binding 参照先のオブジェクトと同じ @ObjectBinding 挙動が不安定だが基本生存しないがち @EnvironmentObject
Navigation・Presentation単位 @Environment Navigation・Presentation単位 あくまで現時点。リリースされるころには変わってそう Viewの構成によって変わることがある
まとめ 値の更新でViewがリビルドされるよう暗黙の更新検知に気をつか って実装すること Viewに紐付くプロパティの生存期間は思った以上に複雑 MVVMとかでviewModelなどをくっつけるときは、viewModelの 生成・破棄が予期せぬタイミングで何度も起こることに注意する 特にsubscribeしただけで通信を発火するみたいな実装をして いると大変なことになるかもしれない・・・・ 環境 macOS
Catalina 10.15 Beta(19A487l) Xcode Version 11.0 beta 2 (11M337n)