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
Mike Apurin
March 29, 2024
Programming
970
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SwiftUIに最適のアーキテクチャ
Mike Apurin
March 29, 2024
More Decks by Mike Apurin
See All by Mike Apurin
iOSDC 2024
auramagi
3
1.5k
SwiftUIとUIKitを仲良くさせる
auramagi
3
6.2k
SwiftUI Layout
auramagi
1
890
SwiftUIでUIViewを使うときのレイアウト処理 / Layout process when using UIKit view in SwiftUI
auramagi
0
770
Other Decks in Programming
See All in Programming
コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo
izumin5210
0
620
まだ間に合う!今年の夏こそSchemeのマクロ展開器を完全理解!
omasanori
0
620
FDEとは、何者なのか?
masapyon1212
0
130
一参加者から『中の人』へ 〜全通PHPerがブースに立って学んだ、カンファレンスを100倍楽しむコツ〜
wp_daisuke
0
110
DroidKaigi 2026 「個人開発という実験場: Android エンジニアが手にする4つの自由」
slashnephy
0
200
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
130
PHPプロジェクトの結合バランスを可視化する #php_night
kajitack
0
200
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
110
書籍「プロフェッショナルAI駆動開発」紹介スライド
juntaromatsumoto
0
960
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
720
AIと壁打ちしながら進めるコスト管理
fufuhu
2
1.9k
[DroidKaigi 26] How We Moved from Android Studio to Slack
thisaay
0
190
Featured
See All Featured
New Earth Scene 8
popppiees
3
2.5k
Context Engineering - Making Every Token Count
addyosmani
9
1.1k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
My Coaching Mixtape
mlcsv
0
300
Google's AI Overviews - The New Search
badams
0
1.6k
Docker and Python
trallard
47
4.2k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
520
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
560
Code Reviewing Like a Champion
maltzj
528
40k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
490
Optimizing for Happiness
mojombo
378
71k
Transcript
SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ Mike Apurin
None
SwiftUIʹ࠷దͷ ΞʔΩςΫνϟͱ Կ͔ʁ
MVVM? – MV? – TCA? – MVC? – MVP? –
VIPER? –
ͦͷલʹ
ۜͷؙͳ͍
ΞʔΩςΫνϟҊ ݅ͷχʔζΛߟྀ͢ ͖
SwiftUIҎ֎ʹUIKitΘΕ͍ͯΔ͔ʁ – ΞʔΩςΫνϟ͕ղܾ͍ͯ͠Δ՝χʔζ ʹϚον͍ͯ͠Δ͔ – νʔϜϝϯόʔ͜ͷΞʔΩςΫνϟʹ׳Ε ͍ͯΔ͔ – etc –
ཧͱݱ࣮ဃ͠ ͍ͯΔ
UIKitͱSwiftUIͷޓ ੑ
UIKitͱSwiftUIͷޓ ੑ UINavigationController ͰUIHostingControllerΛ push͢ΔΞχϝʔγϣϯ͕όά Δ let vc = UIHostingController(rootView:
SwiftUIScreen()) navigationController.show(vc, sender: nil)
SwiftUIͷதͰNavigationLinkΛ౿Ήͱൃੜ ͠ͳ͍ – Ҋ݅ͷ߹্ɺUINavigationControllerΛ ͏͔͠ͳ͍ – ͨͩͦ͏͢Δͱɺ৽ΊͷNavigationStack UINavigationControllerͱޓੑ͕ͳ͍͔ Β NavigationLink(value:)
͑ͳ͍ –
ࠓߋݹ͍ NavigationLink(destination:) ͍ ͨ͘ͳ͍ͷͰࣗલͰ NavigationLink(value:) Λ NavigationStack͕ͳͯ͑͘ΔΑ͏ʹόοΫ ϙʔτ͍ͯ͠·͢ github.com/auramagi/Destinations
SwiftPMͷػೳ
None
Development AssetsΛѻ͏ػೳ͕ͳ͍ DebugϏϧυϓϨϏϡʔʹ͏ը૾ ϑΝΠϧ ReleaseϏϧυ͔Βࣗಈతʹഉআ͞ΕΔ
͍͔ͭ͘ϫʔΫΞϥϯυΛࢼ͍ͯ͠Δ Package.swiftͰΓସ͑͢Δʢඇਪʣ – XcodeͷLaunch argumentͰPackage.swiftΛΓସ͑Δ – ϏϧυϓϥάΠϯ – github.com/auramagi/ManageDevelopmentAssetsPlugin –
खಈͰϑΥϧμΛೖΕସ͑Δ – github.com/auramagi/swiftui-first-sample –
ࢲ͕ٻΊ͍ͯΔ SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ
ཁ݅ SwiftUI First – Xcode Previews͕ਏ͘ͳ͍ – ϚϧνΠϯυ + Ϛϧνεςʔτ͕Մೳ
– UIKit, App Intentsߟ͑ΒΕ͍ͯΔ – ֎෦ϥΠϒϥϦͷґଘ͕ೱ͘ͳ͍ –
SwiftUI First SwiftUIͷঢ়ଶཧ͕͑Δ – SwiftUIͷϥΠϑαΠΫϧͰΓཱͬͯ ͍Δ – SwiftUIͰѻ͍͍͢ –
Previews͕ਏ͘ͳ͍ DI͕ͪΌΜͱ͍ͯ͠Δ – UIϨΠϠʔʹॏ͍ґଘΛஔ͔ͳ͍ – ϞοΫ͍͢͠ –
ϚϧνΠϯυՄೳ ෳΠϯυͰͦΕͧΕϩάΠϯঢ়ଶ ͕ҧ͏ͷઃܭՄೳʁ –
UIKit, App Intents DIೖͷํͲ͏ͳΔʁ – ઃܭͷॊೈੑ͕ඞཁ –
֎෦ϥΠϒϥϦͷґଘ ֎෦ϥΠϒϥϦΛத৺ʹΞϓϦΛઃܭͨ͠Β͍͍͔͕ ৻ॏʹݕ౼͠·͠ΐ͏ – ։ൃࢭ·ͬͯόά͕ग़ͨΒͲ͏͢Δʁ – ണ͕͢ͱ͖ʹͲΕ͚ͩେมʁ – Apple͕৽͍͠APIΛग़ͨ͠Β࠾༻ʹ͠ͳ͍͔ʁ –
github.com/auramagi/ swiftui-first-sample
Naive MV struct ContentView: View { let api: APIClient @State
var products: [Product] = [] var body: some View { ForEach(products) { product in Button("Buy \(product.name)") { Task { await api.execute( BuyProductsRequest(product) ) } } } .task { products = await api.execute( GetProductsRequest() ) } } }
Naive MV ີ݁߹ – ϓϨϏϡʔແཧ – ϏϡʔϩδοΫ࠶༻ੑ͕͍ – ґଘೖ͕ߟ͑ΒΕ͍ͯͳ͍ –
None
None
DIೖ struct AppActions { struct Products { var buy: (Product)
async -> Void var refresh: () async -> Void } } ActionsΛΓ͚Δ –
DIೖ struct AppState { final class Products: ObservableObject { @Published
var products: [Product] = [] } } StateΛΓ͚Δ –
struct ContentView: View { @ObservedObject var state: AppState.Products let actions:
AppActions.Products var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
ΠχγϟϥΠβͰActionsͱStateೖՄೳʹͨ͠ ͕ɺதͷ࣮มΘΔͷ͕كͳͷͰຖճຖճࢦఆ͢Δ ͷ໘ EnvironmentͰґଘΛୡͤ͞Δ – State @EnvironmentObject Ͱ – Actions
@Environment Ͱ –
StateͱActionsΛଋͶͯ Dependency ͱͯ͠ఆٛ struct AppState { final class Products: ObservableObject
{ ... } var products = Products() } struct AppActions { struct Products { ... } var products: Products } extension EnvrionmentValues { var appActions: AppActions { ... } } struct AppDependency { var state: AppState var actions: AppActions }
Dependency Λอ࣋͢ΔίϯςφΛ ఆٛ protocol AppContainer { var app: AppDependency {
get set } } MockAppContainer ͱ LiveAppContainer Λ ४උ
struct ContentView: View { @EnvironmentObject var state: AppState.Products @Environment(\.appActions.products) let
actions var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
ೖΛ͘͢͢͠Δ public protocol ViewInjectable { typealias Content = ViewDependencyModifier<Self>.Content associatedtype
InjectedBody: View func inject(content: Content) -> InjectedBody } public struct ViewDependencyModifier<D: ViewInjectable>: ViewModifier { let dependency: D public func body(content: Content) -> some View { dependency.inject(content: content) } } extension View { public func dependency(_ dependency: some ViewInjectable) -> some View { modifier(ViewDependencyModifier(dependency: dependency)) } }
Service Λ࣮ final class ProductsService { let api: APIClient let
state: AppState.Products func buy(product: Product) async { await api.execute( BuyProductsRequest(product) ) } func refresh() async { products = await api.execute( GetProductsRequest() ) } }
LiveAppContainer Ͱଓ final class LiveAppContainer: AppContainer { struct Configuration {
... } var app: AppDependency private let api: APIClient private let productsService: ProductsService init(configuration: Configuration) { self.app = .init() self.api = ... self.productsService = .init( api: api, state: app.state.products ) app.actions.products.buy = productsService.buy(product:) app.actions.products.refresh = productsService.refresh } }
Previews #Preview { ContentView() .mockContainer(.app) } #Preview { WithMockContainer(.app) {
container in MainFlow(container: container) } } ( .mockContainer ͱ WithMockContainer ͷׂ࣮Ѫ)
ίϯςφFlowͰ͢ struct MainFlow<Container: AppContainer>: View { let container: Container var
body: some View { ContentView() .dependency(container) } }
View – ͨͩͷUI෦ – Screen – ը໘શମΛදࣔ͢ΔUI෦ – Screen ಉ࢜Λ࡞͠ͳ͍
– Flow – DependencyContainer Λอ࣋ͯ͠ೖ͢Δ – Screen Λ࡞͢Δ – ભҠ –
AppͰίϯςφΛੜ @main struct SampleApp: App { @State var container =
LiveAppContainer( configuration: ... ) var body: some Scene { WindowGroup { MainFlow(container: container) .dependency(container) } } }
None
͝ਗ਼ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠