Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
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
370
わいわいswiftc#35夢が広がる!コード生成でどこでもSwift
sidepelican
0
490
元ゲーム開発者が贈る描画パフォーマンス改善 / Rendering performance improvement from a game developer
sidepelican
4
1.9k
わいわいswiftc#19Genericsの特殊化
sidepelican
0
500
わいわいswiftc#17Genericsの特殊化
sidepelican
0
110
クックパッドiOSアプリのパフォーマンス改善
sidepelican
0
820
DispatchQueue.syncが動作するスレッド
sidepelican
0
400
Other Decks in Programming
See All in Programming
Intent as Code
shoppingjaws
6
1.1k
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
810
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
520
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
330
選挙速報を多くのユーザーへ 届ける Live Activities 設計
hamayokokuririn
0
190
GitHubハンズオン講座 — 実務レベルのチーム開発のフローを身につけよう
junhat6
0
110
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
600
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
220
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
8
5.3k
Vue Fes Japan 2026 タイムテーブル徹底解説
448jp
1
580
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
140
iOSDC2026登壇資料.pdf
riofujimon
0
200
Featured
See All Featured
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
260
We Are The Robots
honzajavorek
0
380
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.7k
Documentation Writing (for coders)
carmenintech
77
5.6k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
3
4.3k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
33
5k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
Between Models and Reality
mayunak
4
480
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
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)