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
redacted を TCA でスマートに扱う
Search
Aikawa
October 24, 2020
Programming
1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
redacted を TCA でスマートに扱う
Aikawa
October 24, 2020
More Decks by Aikawa
See All by Aikawa
DocC Tutorial と TCA におけるテスト機能の紹介
kalupas226
1
1.8k
Swift愛好会WWDC要約会 Build programmatic UI with Xcode Previews
kalupas226
2
900
enum で KeyPaths のような機能を実現する CasePaths
kalupas226
4
1k
SwiftUI Navigation のすべて
kalupas226
11
9.7k
Refreshable API を TCA で使う
kalupas226
0
300
Combineを使ったコードのテストをSchedulerで操る方法とその仕組み
kalupas226
2
1.9k
Composable FormsでTCAのボイラープレートとおさらばする
kalupas226
1
1.4k
Swiftの関数と代数学
kalupas226
0
890
Swiftのstruct・enumと代数学_part1
kalupas226
2
1.6k
Other Decks in Programming
See All in Programming
Laravelのアプリケーションをどこにデプロイするか #ツナギメオフライン.9
akase244
0
120
AIに既存システムを理解させる技術 ~レガシーを見捨てないハーネスエンジニアリング入門~
ochtum
0
210
片田舎のおっさん、 Swift Buildのダイアモンド問題解決の不具合修正PRを出すが、解決方法がキャッシュをしないようにすることであり、ビルド時間が伸びると言われてマージされないので高速化もする/swiftbuild
yimajo
0
360
Hono + Inertia + React で LP を構築した話
oukayuka
2
220
20260828_品質と開発生産性を両立させる、AI時代のE2Eテストの考え方
magicpod
0
170
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
160
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
880
高専キャリア LT 発表内容
crysta1221
6
5.6k
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
4.3k
アクセシビリティから考える情報設計
high_g_engineer
0
300
Oxlintはいいぞ(続)
yug1224
1
560
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
930
Featured
See All Featured
The Language of Interfaces
destraynor
162
27k
Skip the Path - Find Your Career Trail
mkilby
1
220
Why Our Code Smells
bkeepers
PRO
340
58k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
260
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.7k
Code Review Best Practice
trishagee
74
20k
Crafting Experiences
bethany
1
300
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.4k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
430
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
490
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
270
Transcript
redacted を TCA でスマートに扱う iOS アプリ開発のためのFunctional Architecture 情報共有会
⾃⼰紹介 アイカワ(@kalupas0930 ) 新卒 iOS エンジニア 函館出⾝ 最近は Flutter, 機械学習の勉強をしてます
SwiftUI と Combine もまだまだ勉強中です 2
redacted とは? SkeltetonView のようなもの SwiftUI で iOS14 から使⽤できる ViewModifier とても便利
3
4
SwiftUI でどう使う? Text("This is redacted") .redacted(reason: .placeholder) redacted Modifier を付けるだけ
5
ViewModifier ならではの使い⽅ VStack { Image("kuma") .resizable() .frame(width: 100, height: 100)
.clipShape(Circle()) Text("This is redacted") Text("kuma") } 6
ViewModifier ならではの使い⽅ ViewModifier なので簡単に Skeleton を 表現できる VStack { Image("kuma")
.resizable() .frame(width: 100, height: 100) .clipShape(Circle()) Text("This is redacted") Text("kuma") } .redacted(reason: .placeholder) 7
もう少しだけ redacted について深掘り redacted は以下のように定義されている func redacted(reason: RedactionReasons) -> some
View RedactionReasons は? 現時点では先ほど紹介した placeholder しか持っていない OptionSet に適合しているため、将来的には他の reason も 使えるようになるかもしれない ちなみに現時点でもオリジナルの ReactionReasons を作って、 reason を使い分けることはできる 8
状態管理含めた時の redacted を⾒ていきます まずは @ObservableObejct を利⽤した TCA を利⽤しない SwiftUI での使⽤⽅法を⾒ていく
単純なリスト表⽰をするだけのアプリを作る 9
10
扱う状態 struct Item: Equatable, Identifiable { let id: UUID let
title: String let description: String } 基本的な title と description を持っているだけ 11
プレースホルダー⽤の変数 let placeholderListItem = (0...10).map { _ in Item( id:
.init(), title: String(repeating: " ", count: .random(in: 50...100)), description: String(repeating: " ", count: .random(in: 10...30)) ) } title と description は適当にスペースで埋めてそれっぽくしている 12
ロード完了後⽤の変数 let liveListItem = [ Item(id: .init(), title: " これは
redacted", description: String(repeating: " おはよう", count: 10)), Item(id: .init(), title: "This is redacted", description: String(repeating: "Good morning", count: 10)), Item(id: .init(), title: " よろしくお願いします", description: String(repeating: "yes,yes", count: 10)) ] 中⾝は適当です 13
状態管理⽤の ObservableObject class ListItemViewModel: ObservableObject { @Published var listItem: [Item]
= [] @Published var isLoading = false init() { isLoading = true // 4s 経ったら⾃動的に動作するようにする DispatchQueue.main.asyncAfter(deadline: .now() + 4){ self.isLoading = false self.listItem = liveListItem } } } 14
View を少しずつ⾒ていきます @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } } } 15
ForEach の中⾝ / ボタンのアクション @ObservedObject private var viewModel = ListItemViewModel()
... // 省略 ForEach( viewModel.isLoading ? placeholderListItem : viewModel.listItem) { item in Button(action: { guard !self.viewModel.isLoading else { return } print("Button was tapped") }) { ... // ボタンの View } } 16
ForEach の中⾝ / ボタンの View @ObservedObject private var viewModel =
ListItemViewModel() ... // 省略 ForEach( ... ) { item in Button(action: { ... }) { HStack(alignment: .top) { Image("kuma") .resizable() .frame(width: 80, height: 80) VStack(alignment: .leading, spacing: 10) { Text(item.title).font(.title2) Text(item.description).font(.body) } } } } 17
redacted を追加 @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } .redacted(reason: viewModel.isLoading ? .placeholder: []) } } 18
disabled も追加 @ObservedObject private var viewModel = ListItemViewModel() var body:
some View { List { if viewModel.isLoading { ActivityIndicator().frame(maxWidth: .infinity).padding() } ForEach( ... // 繰り返す Item Button(action: { ... // ボタンを押した時のアクション }) { ... // ボタンの View } .redacted(reason: viewModel.isLoading ? .placeholder: []) .disabled(viewModel.isLoading) // これを追加 } 19
やりたいことは実現できた しかし、この⽅法には問題点がある View のあちこちで viewModel.isLoading を使っている 状態が増えてきた時に開発者が気にしなければならないことが 多くなってしまう disabled によってロード中はタップできないようにできたが、
disabled の利⽤シーンとしては微妙 もし onAppear などがあった際、それを防ぐことはできない ⾊が少し明るくなってしまうので、本来意図している View の⾊ とは異なるものになるかもしれない 20
The Composable Architecture なら? 基本的な TCA の流れ View から Action
を送る Action によって Reducer で Store の State が変更される イメージは isLoading によって Store を使い分ける isLoading が true ( ロード中) : プレースホルダー⽤の Store false ( ロード完了): 本物の Store 21
実際に TCA を使った例を紹介します まずは State struct ListItemState: Equatable { var
listItem: [Item] = [] var isLoading = false } 先ほどの @ObservableObject を利⽤した class と⼤きな差はない State は Action を通じてのみ変更されるため、struct 内に 状態を変化させるための関数はない 22
Action enum ListItemAction { case listItemResponse([Item]?) case onAppear } View
の onAppear 時に呼ばれる Action その Action によって発⽕する listItemResponse([Item]?) 23
Reducer let listItemReducer = Reducer<ListItemState, ListItemAction, Void> { state, action,
environment in switch action { case let .listItemResponse(listItem): state.isLoading = false state.listItem = listItem ?? [] return .none case .onAppear: state.isLoading = true return Effect(value: .listItemResponse(liveListItem)) .delay(for: 4, scheduler: DispatchQueue.main) .eraseToEffect() } } onAppear してから、わざと 4s 遅らせるようにして API 通信してる⾵ にしているだけ 24
View の全体像 let store: Store<ListItemState, ListItemAction> var body: some View
{ WithViewStore(store) { viewStore in List { if viewStore.isLoading { ActivityIndicator().padding().frame(maxWidth: .infinity) } ListItemView( // ( プレースホルダー store) or ( 本物 store) を渡す ) .redacted(reason: viewStore.isLoading ? .placeholder : []) } .onAppear { viewStore.send(.onAppear) } } } 25
ListItemView への Store の渡し⽅ ListItemView( store: viewStore.isLoading ? Store( initialState:
.init(listItem: placeholderListItem), reducer: .empty, environment: () ) : self.store ) .redacted(reason: viewStore.isLoading ? .placeholder : []) viewStore.isLoading によって以下を渡す true : placeholder ⽤ Store false : 本物の Store 26
⼀応 ListItemView の中⾝ let store: Store<ListItemState, ListItemAction> var body: some
View { WithViewStore(store) { viewStore in ForEach(viewStore.listItem) { item in // 本当は ForEachStore などを使うと良い Button(action: { // ここで viewStore.send() としても、placeholder store であれば // state に影響はないので、send し放題 }) { HStack(alignment: .top) { Image("kuma").resizable().frame(width: 80, height: 80) VStack(alignment: .leading, spacing: 10) { Text(item.title).font(.title2) Text(item.description).font(.body) } } } .buttonStyle(PlainButtonStyle()) } } 27
TCA と redacted を組み合わせれば 開発者は最初に isLoading の状態によって、Store を使い分ける という判断だけで良くなる disabled
を使⽤せずとも、「ローディング中のセルをタップ しても何も起きないようにする」という動作を実現できた 今回は扱う State を説明のために絞ったが、State が多くなれば なるほど TCA の恩恵を受けることができる 28
おわりに 今回発表した内容は Point-Free さんの Episode115~ の redacted についての記事を参考にしました https://www.pointfree.co/ 記事ではもっと深掘りされた内容が書かれています
扱う状態が増えた時の redacted の扱い⽅ 画⾯も⼀つではなく複数 コンテンツの多くは有料かつ動画は英語ですが、スクリプトが あるので英語が苦⼿でも翻訳頼りで理解できます 29