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
960
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
書籍「プロフェッショナルAI駆動開発」紹介スライド
juntaromatsumoto
0
820
Hono + Inertia + React で LP を構築した話
oukayuka
2
190
Gmail/Google DriveをトリガーにAIエージェントを動かそう! / Run AI agents with Gmail/Google Drive as triggers!
har1101
3
460
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.3k
ソフトウェアラスタライザ
fadis
1
730
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
240
【QA Test Talk Vol.8】AI-DLC による Whole Team Approach の加速
pkshadeck
PRO
0
260
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
400
不幸な GC
chencmd
0
830
プロポーザルを書いてもらう
pvcresin
0
590
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.2k
業務時間外もAIに働いてもらう話
colorful12
2
9k
Featured
See All Featured
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
500
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
670
The World Runs on Bad Software
bkeepers
PRO
72
12k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Prompt Engineering for Job Search
mfonobong
0
420
A Tale of Four Properties
chriscoyier
163
24k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
660
Done Done
chrislema
186
16k
Between Models and Reality
mayunak
4
410
Skip the Path - Find Your Career Trail
mkilby
1
200
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
280
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
630
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
͝ਗ਼ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠