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
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
480
元ゲーム開発者が贈る描画パフォーマンス改善 / 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
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
180
Go 1.27 における memory allocation の高速化
andpad
0
250
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
330
How I Won Prize Money at a Hackathon Using Codex and Symphony Alpha
yasei_no_otoko
0
110
VibeCodingからAgenticWorkflowへ
starfish719
0
650
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
1
1.8k
AI時代に設計が 最大の生産性レバーになる 意図駆動開発とデータを消さない設計|Don't Delete Your Data or Your Intent — Design as the Deepest Lever in the AI Era
tomohisa
1
1k
the container ship “Apple Silicon”@WWDC26 Recap -Japan-\(region).swift
shingangan
0
130
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
270
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
0
1.9k
GDG Korea Android: 2026 I/O Extended ~ What's new in Android development tools
pluu
0
230
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
480
Featured
See All Featured
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
SEO for Brand Visibility & Recognition
aleyda
0
4.7k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
66
57k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
470
Prompt Engineering for Job Search
mfonobong
0
410
Being A Developer After 40
akosma
91
590k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
390
Large-scale JavaScript Application Architecture
addyosmani
515
110k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
Ethics towards AI in product and experience design
skipperchong
2
350
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)