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
Swift Concurrencyを利用したMVVMについて考える
Search
coffmark
September 20, 2024
490
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Swift Concurrencyを利用したMVVMについて考える
Chiba.swift #1で発表したスライドです。
https://chibaswift.connpass.com/event/328367
coffmark
September 20, 2024
More Decks by coffmark
See All by coffmark
RenovateをiOSアプリ開発に導入・運用した話
coffmark
0
450
Featured
See All Featured
Building Applications with DynamoDB
mza
96
7.2k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
WCS-LA-2024
lcolladotor
0
890
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
540
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Abbi's Birthday
coloredviolet
4
10k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
640
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
450
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8.1k
Embracing the Ebb and Flow
colly
88
5.2k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
330
Transcript
Swi$ ConcurrencyΛར༻ͨ͠ MVVMʹ͍ͭͯߟ͑Δ Chiba.swi) #1
ࣗݾհ • coffmarkͱ͍͍·͢ɻ • υϫϯΰͰಇ͍͍ͯ·͢ɻ • 22৽ଔͰ͢ɻ • ઍ༿ݝʹॅΜͰ͍·͢ɻ
ڥ • Xcode 15.4 • Swi/ 5.10 • ίϯύΠϥϑϥά •
-strict-concurrency=targeted
࣍ • RxSwi'Λར༻ͨ͠MVVM • Swi' ConcurrencyΛར༻ͨ͠MVVM • ํ • ࣮༰
(APIݺͼग़͠ViewModelͷϝιουݺͼग़͠) • Preview • ·ͱΊ
RxSwi&Λར༻ͨ͠MVVMͷશମ૾
Swi$ ConcurrencyΛ ར༻ͨ͠MVVM
ํ • task(priority:_:)Λ͍ɺΩϟϯηϧॲཧΛࣗલ࣮͢Δ͜ͱ͕গ ͳ͍ܗͰಋೖΛਐΊ͍ͨͱߟ͍͑ͯ·͢ɻ • View͕ඇදࣔʹͳͬͨλΠϛϯάͰඇಉظॲཧ͕Ωϟϯηϧ ͞ΕΔ͔ΒͰ͢ɻ
Swi$ ConcurrencyΛར༻ͨ͠MVVMͷશମ૾
Viewͷॳظ࣮ struct ChibaView: View { /// Viewͷঢ়ଶ enum ViewState {
/// ಡΈࠐΈத case loading /// ಡΈࠐΈޭ࣌ /// - response: Ϩεϙϯε /// - isLoading: ϩʔσΟϯάΠϯδέʔλͷදࣔ༗ແ case success(response: Response, isLoading: Bool) /// ಡΈࠐΈࣦഊ࣌ /// - isLoading: ϩʔσΟϯάΠϯδέʔλͷදࣔ༗ແ case error(isLoading: Bool) } var body: some View { ... } }
ViewModelͷॳظ࣮ @MainActor final class ChibaViewModel: ObservableObject { /// ViewState @Published
private(set) var viewState: ChibaView.ViewState = .loading /// ΤϥʔΞϥʔτදࣔϑϥά @Published var isPresentAlert: Bool = false private let repository: RepositoryProtocol init(repository: RepositoryProtocol) { ... } }
APIݺͼग़͠ extension ChibaViewModel { // Actorִ͞Εͳ͍ nonisolated func resolveResponse() async
throws -> Response { // (Repositoryʹ࣮) func resolve() -> Single<Response> try await repository.resolve().value } }
Swi$ ConcurrencyΛར༻ͨ͠MVVMͷશମ૾ (࠶ܝ)
View͔ΒViewModelͷϝιουݺͼग़͠ ଞʹ͋Δͱࢥ͍·͕͢ɺࠓճҎԼͷ3ͭΛߟ͑·͢ɻ 1. ॳճಡΈࠐΈ 2. Pull to Refresh 3. ΤϥʔΞϥʔτͷϦτϥΠ
ॳճಡΈࠐΈ (View࣮) struct ChibaView: View { @StateObject viewModel: ChibaViewModel //
ը໘͕ॳճදࣔͨ͠λΠϛϯάͰॳճಡΈࠐΈΛ࣮ߦ͢ΔͨΊͷϑϥά @State private var viewDidLoad: Bool = false var body: some View { Group { switch viewModel.viewState { ... } } .task { if !viewDidLoad { viewDidLoad.toggle() await viewModel.initialLoad() } } } }
ॳճಡΈࠐΈ (ViewModel࣮) final class ChibaViewModel: ObservableObject { /// ॳճಡΈࠐΈΛ࣮ߦ func
initialLoad() async { do { let response = try await resolveResponse() // ޭ࣌ͷը໘Λදࣔ self.viewState = .success(response: response, isLoading: false) } catch { // Τϥʔը໘Λදࣔ self.viewState = .error(isLoading: false) } } }
Pull to Refresh (View࣮) struct ChibaView: View { // ...
var body: some View { switch viewModel.viewState { case .success: successView(...) } } func successView(...) -> some View { ScrollView { ... } .refreshable { await viewModel.refresh() } } }
Pull to Refresh (ViewModel࣮) final class ChibaViewModel: ObservableObject { @Published
var isPresentAlert: Bool = false func refresh() async { do { let response = try await resolveResponse() // ޭ࣌ͷը໘Λදࣔ self.viewState = .success(response: response, isLoading: false) } catch { // ΤϥʔΞϥʔτΛදࣔ isPresentAlert = true } } }
ΤϥʔΞϥʔτͷϦτϥΠ (View࣮) struct ChibaView: View { var body: some View
{ Group { switch viewState { ... } } .alert("Error", isPresented: $viewModel.isPresentAlert) { Button("Cancel", role: .cancel) { } /// task(priority:_:)ͰඇಉظؔΛ࣮ߦ͢ΔϘλϯ AsyncButton { await viewModel.retry() } label: { Text("Retry") } } } }
AsyncBu(on.swi- /// ඇಉظؔΛ࣮ߦ͢ΔϘλϯ struct AsyncButton<Label>: View where Label: View {
@State private var isExecuting = false let action: () async -> Void let label: () -> Label public var body: some View { Button { if isExecuting { return } isExecuting.toggle() } label: { label() } .task(id: isExecuting) { guard isExecuting else { return } await action() isExecuting.toggle() } } }
ΤϥʔΞϥʔτͷϦτϥΠ (ViewModel࣮) final class ChibaViewModel: ObservableObject { func retry() async
{ // ϩʔσΟϯάදࣔ͢ΔͨΊviewStateͷisLoadingΛtrueʹมߋ transitionToLoading() do { let cards = try await resolveResponse() self.viewState = .success(cards: cards, isLoading: false) } catch { // ϩʔσΟϯάΛඇදࣔ͢ΔͨΊviewStateͷisLoadingΛfalseʹมߋ transitionToLoaded() isPresentAlert = true } } }
Preview #Preview { // MockoloΛར༻ let repository = RepositoryProtocolMock() repository.resolve
= { @Sendable in .just(Response(...)) } return ChibaView( viewModel: ChibaViewModel( repository: repository ) ) }
·ͱΊ • task(priority:_:)Λར༻ͯ͠ΩϟϯηϧॲཧΛࣗલ࣮͢Δ͜ͱ͕ গͳ͍ܗͰSwi1 ConcurrencyಋೖΛਐΊ͍ͯ͘͜ͱ͕Ͱ͖·͠ ͨɻ • طଘ࣮ΑΓϩʔΧϧมେ෯ʹগͳ͘ͳΔܗͰViewModel Λ࣮͢Δ͜ͱ͕Ͱ͖·ͨ͠ɻ
ࠓޙ • ը໘ભҠपΓSwi%UIʹد͍͖͍ͤͯͨͱߟ͍͑ͯ·͢ɻ
ࢀߟ • Swi%UI ͷ Bu+on ͳͲͰτϦΨʔ͢Δ async ͳϝιουͷ࣮ߦ ΛɺΩϟϯηϧͷ͜ͱ·Ͱߟ࣮͑ͯ͢Δํ๏ 2બ
• treastrain / Tanaka Ryoga͞Μ • h+ps:/ /zenn.dev/treastrain/arBcles/3effccd39f4056
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ