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
480
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
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.8k
Crafting Experiences
bethany
1
340
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
280
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
490
Designing for humans not robots
tammielis
254
26k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.8k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
500
Prompt Engineering for Job Search
mfonobong
0
450
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
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ