Skip to content

Commit e062511

Browse files
committed
feat: LoadingState를 통해 무지성 Loading 플래그를 관리하는 형태 개선
1 parent c25b9c9 commit e062511

8 files changed

Lines changed: 171 additions & 63 deletions

DevLog/Presentation/ViewModel/AccountViewModel.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ final class AccountViewModel: Store {
5555
private let fetchProvidersUseCase: FetchAuthProvidersUseCase
5656
private let linkProviderUseCase: LinkAuthProviderUseCase
5757
private let unlinkProviderUseCase: UnlinkAuthProviderUseCase
58+
private let loadingState = LoadingState()
5859

5960
init(
6061
fetchProvidersUseCase: FetchAuthProvidersUseCase,
@@ -106,11 +107,10 @@ final class AccountViewModel: Store {
106107
}
107108
}
108109
case .link(let provider):
110+
beginLoading(.delayed)
109111
Task {
110112
do {
111-
defer { send(.setLoading(false)) }
112-
send(.setLoading(true))
113-
113+
defer { endLoading(.delayed) }
114114
try await linkProviderUseCase.execute(provider)
115115
send(.setToast(isPresented: true, type: .linkSuccess))
116116

@@ -122,11 +122,10 @@ final class AccountViewModel: Store {
122122
}
123123
}
124124
case .unlink(let provider):
125+
beginLoading(.delayed)
125126
Task {
126127
do {
127-
defer { send(.setLoading(false)) }
128-
send(.setLoading(true))
129-
128+
defer { endLoading(.delayed) }
130129
try await unlinkProviderUseCase.execute(provider)
131130
send(.setToast(isPresented: true, type: .unlinkSuccess))
132131

@@ -192,4 +191,16 @@ private extension AccountViewModel {
192191
state.showToast = isPresented
193192
state.toastType = type
194193
}
194+
195+
private func beginLoading(_ mode: LoadingState.Mode) {
196+
loadingState.begin(mode: mode) { [weak self] isLoading in
197+
self?.send(.setLoading(isLoading))
198+
}
199+
}
200+
201+
private func endLoading(_ mode: LoadingState.Mode) {
202+
loadingState.end(mode: mode) { [weak self] isLoading in
203+
self?.send(.setLoading(isLoading))
204+
}
205+
}
195206
}

DevLog/Presentation/ViewModel/HomeViewModel.swift

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ final class HomeViewModel: Store {
8181
case searchView
8282
}
8383

84-
enum LoadingTarget {
84+
enum LoadingTarget: Hashable {
8585
case recentTodos
8686
case webPage
8787
case overlay
@@ -94,6 +94,7 @@ final class HomeViewModel: Store {
9494
private let undoDeleteWebPageUseCase: UndoDeleteWebPageUseCase
9595
private let fetchTodosUseCase: FetchTodosUseCase
9696
private let fetchWebPagesUseCase: FetchWebPagesUseCase
97+
private let loadingState = LoadingState()
9798
private var deletedWebPageURLString: String?
9899

99100
init(
@@ -133,10 +134,10 @@ final class HomeViewModel: Store {
133134
func run(_ effect: SideEffect) {
134135
switch effect {
135136
case .addTodo(let todo):
137+
beginLoading(for: .overlay, mode: .delayed)
136138
Task {
137139
do {
138-
defer { send(.setLoading(.overlay, false)) }
139-
send(.setLoading(.overlay, true))
140+
defer { endLoading(for: .overlay, mode: .delayed) }
140141
try await upsertTodoUseCase.execute(todo)
141142
let page = try await fetchRecentTodos()
142143
let items = page.items
@@ -149,10 +150,10 @@ final class HomeViewModel: Store {
149150
}
150151
}
151152
case .fetchRecentTodos:
153+
beginLoading(for: .recentTodos, mode: .immediate)
152154
Task {
153155
do {
154-
defer { send(.setLoading(.recentTodos, false)) }
155-
send(.setLoading(.recentTodos, true))
156+
defer { endLoading(for: .recentTodos, mode: .immediate) }
156157
let page = try await fetchRecentTodos()
157158
let items = page.items
158159
.filter { $0.createdAt != $0.updatedAt }
@@ -164,10 +165,10 @@ final class HomeViewModel: Store {
164165
}
165166
}
166167
case .addWebPage(let urlString):
168+
beginLoading(for: .overlay, mode: .delayed)
167169
Task {
168170
do {
169-
defer { send(.setLoading(.overlay, false)) }
170-
send(.setLoading(.overlay, true))
171+
defer { endLoading(for: .overlay, mode: .delayed) }
171172
try await addWebPageUseCase.execute(urlString)
172173
let pages = try await fetchWebPagesUseCase.execute("")
173174
send(.updateWebPages(pages.map { WebPageItem(from: $0) }))
@@ -176,20 +177,20 @@ final class HomeViewModel: Store {
176177
}
177178
}
178179
case .deleteWebPage(let page, let index):
180+
beginLoading(for: .webPage, mode: .delayed)
179181
Task {
180182
do {
181-
defer { send(.setLoading(.webPage, false)) }
182-
send(.setLoading(.webPage, true))
183+
defer { endLoading(for: .webPage, mode: .delayed) }
183184
try await deleteWebPageUseCase.execute(page.url.absoluteString)
184185
} catch {
185186
send(.restoreWebPage(page, index))
186187
send(.setAlert(isPresented: true, type: .error))
187188
}
188189
}
189190
case .undoDeleteWebPage(let urlString):
191+
beginLoading(for: .webPage, mode: .delayed)
190192
Task {
191-
defer { send(.setLoading(.webPage, false)) }
192-
send(.setLoading(.webPage, true))
193+
defer { endLoading(for: .webPage, mode: .delayed) }
193194

194195
var shouldPresentError = false
195196

@@ -211,10 +212,10 @@ final class HomeViewModel: Store {
211212
}
212213
}
213214
case .fetchWebPages:
215+
beginLoading(for: .webPage, mode: .immediate)
214216
Task {
215217
do {
216-
defer { send(.setLoading(.webPage, false)) }
217-
send(.setLoading(.webPage, true))
218+
defer { endLoading(for: .webPage, mode: .immediate) }
218219
let pages = try await fetchWebPagesUseCase.execute("")
219220
send(.updateWebPages(pages.map { WebPageItem(from: $0) }))
220221
} catch {
@@ -411,4 +412,22 @@ private extension HomeViewModel {
411412
cursor: nil
412413
)
413414
}
415+
416+
private func beginLoading(
417+
for target: LoadingTarget,
418+
mode: LoadingState.Mode
419+
) {
420+
loadingState.begin(target: target, mode: mode) { [weak self] target, isLoading in
421+
self?.send(.setLoading(target, isLoading))
422+
}
423+
}
424+
425+
private func endLoading(
426+
for target: LoadingTarget,
427+
mode: LoadingState.Mode
428+
) {
429+
loadingState.end(target: target, mode: mode) { [weak self] target, isLoading in
430+
self?.send(.setLoading(target, isLoading))
431+
}
432+
}
414433
}

DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ final class PushNotificationListViewModel: Store {
6060
private let toggleReadUseCase: TogglePushNotificationReadUseCase
6161
private let fetchQueryUseCase: FetchPushNotificationQueryUseCase
6262
private let updateQueryUseCase: UpdatePushNotificationQueryUseCase
63+
private let loadingState = LoadingState()
6364
private var undoDeleteNotificationId: String?
6465
private var cancellable: AnyCancellable?
6566

@@ -117,10 +118,10 @@ final class PushNotificationListViewModel: Store {
117118
if cursor == nil {
118119
stopObservingNotifications()
119120
}
121+
beginLoading(.immediate)
120122
Task {
121123
do {
122-
defer { send(.setLoading(false)) }
123-
send(.setLoading(true))
124+
defer { endLoading(.immediate) }
124125
let existingCount = cursor == nil ? 0 : self.state.notifications.count
125126

126127
let page = try await fetchUseCase.execute(query, cursor: cursor)
@@ -145,35 +146,35 @@ final class PushNotificationListViewModel: Store {
145146

146147
}
147148
case .delete(let item, let index):
149+
beginLoading(.delayed)
148150
Task {
149151
do {
150-
defer { send(.setLoading(false)) }
151-
send(.setLoading(true))
152+
defer { endLoading(.delayed) }
152153
try await deleteUseCase.execute(item.id)
153154
} catch {
154155
send(.restoreNotification(item, index))
155156
send(.setAlert(isPresented: true))
156157
}
157158
}
158159
case .undoDelete(let notificationId):
160+
beginLoading(.delayed)
159161
Task {
160-
// defer을 통해 setLoading을 false로 제어하지 않는 이유
161-
// send(.fetchNotifications)를 통해 false로 처리될 것이기 때문
162-
send(.setLoading(true))
163-
162+
// endLoading(.delayed)를 defer로 두지 않는 이유
163+
// send(.fetchNotifications)로 이어지는 즉시 로딩이 같은 isLoading을 이어서 제어해야 하기 때문
164164
do {
165165
try await undoDeleteUseCase.execute(notificationId)
166166
} catch {
167167
send(.setAlert(isPresented: true))
168168
}
169169

170170
send(.fetchNotifications)
171+
endLoading(.delayed)
171172
}
172173
case .toggleRead(let todoId):
174+
beginLoading(.delayed)
173175
Task {
174176
do {
175-
defer { send(.setLoading(false)) }
176-
send(.setLoading(true))
177+
defer { endLoading(.delayed) }
177178
try await toggleReadUseCase.execute(todoId)
178179
} catch {
179180
send(.setAlert(isPresented: true))
@@ -338,6 +339,18 @@ private extension PushNotificationListViewModel {
338339
cancellable?.cancel()
339340
cancellable = nil
340341
}
342+
343+
private func beginLoading(_ mode: LoadingState.Mode) {
344+
loadingState.begin(mode: mode) { [weak self] isLoading in
345+
self?.send(.setLoading(isLoading))
346+
}
347+
}
348+
349+
private func endLoading(_ mode: LoadingState.Mode) {
350+
loadingState.end(mode: mode) { [weak self] isLoading in
351+
self?.send(.setLoading(isLoading))
352+
}
353+
}
341354
}
342355

343356
extension PushNotificationQuery.SortOrder {

DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ final class PushNotificationSettingsViewModel: Store {
5050
private let calendar = Calendar.current
5151
private let fetchPushSettingsUseCase: FetchPushSettingsUseCase
5252
private let updatePushSettingsUseCase: UpdatePushSettingsUseCase
53+
private let loadingState = LoadingState()
5354

5455
init(
5556
fetchPushSettingsUseCase: FetchPushSettingsUseCase,
@@ -106,10 +107,10 @@ final class PushNotificationSettingsViewModel: Store {
106107
func run(_ effect: SideEffect) {
107108
switch effect {
108109
case .fetchPushNotificationSettings:
110+
beginLoading(.immediate)
109111
Task {
110112
do {
111-
defer { send(.setLoading(false)) }
112-
send(.setLoading(true))
113+
defer { endLoading(.immediate) }
113114
let settings = try await fetchPushSettingsUseCase.execute()
114115
self.send(.setPushNotificationEnable(settings.isEnabled))
115116
if let hour = settings.scheduledTime.hour,
@@ -122,10 +123,10 @@ final class PushNotificationSettingsViewModel: Store {
122123
}
123124
}
124125
case .updatePushNotificationSettings:
126+
beginLoading(.delayed)
125127
Task {
126128
do {
127-
defer { send(.setLoading(false)) }
128-
send(.setLoading(true))
129+
defer { endLoading(.delayed) }
129130
let dateComponents = calendar.dateComponents(
130131
[.hour, .minute],
131132
from: state.sheetPushNotificationTime
@@ -153,4 +154,16 @@ extension PushNotificationSettingsViewModel {
153154
state.alertMessage = "문제가 발생했습니다. 잠시 후 다시 시도해주세요."
154155
state.showAlert = isPresented
155156
}
157+
158+
private func beginLoading(_ mode: LoadingState.Mode) {
159+
loadingState.begin(mode: mode) { [weak self] isLoading in
160+
self?.send(.setLoading(isLoading))
161+
}
162+
}
163+
164+
private func endLoading(_ mode: LoadingState.Mode) {
165+
loadingState.end(mode: mode) { [weak self] isLoading in
166+
self?.send(.setLoading(isLoading))
167+
}
168+
}
156169
}

DevLog/Presentation/ViewModel/SettingViewModel.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ final class SettingViewModel: Store {
4646
private let sessionUseCase: AuthSessionUseCase
4747
private let observeSystemThemeUseCase: ObserveSystemThemeUseCase
4848
private let updateSystemThemeUseCase: UpdateSystemThemeUseCase
49+
private let loadingState = LoadingState()
4950
private var cancellables = Set<AnyCancellable>()
5051

5152
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
@@ -104,23 +105,23 @@ final class SettingViewModel: Store {
104105
func run(_ effect: SideEffect) {
105106
switch effect {
106107
case .deleteAuth:
108+
beginLoading(.delayed)
107109
Task {
108110
do {
109-
defer { send(.setLoading(false)) }
110111
send(.setAlert(isPresented: false))
111-
send(.setLoading(true))
112+
defer { endLoading(.delayed) }
112113
try await deleteAuthuseCase.execute()
113114
sessionUseCase.execute(false)
114115
} catch {
115116
send(.setAlert(isPresented: true, type: .error))
116117
}
117118
}
118119
case .signOut:
120+
beginLoading(.delayed)
119121
Task {
120122
do {
121-
defer { send(.setLoading(false)) }
122123
send(.setAlert(isPresented: false))
123-
send(.setLoading(true))
124+
defer { endLoading(.delayed) }
124125
try await signOutUseCase.execute()
125126
sessionUseCase.execute(false)
126127
} catch {
@@ -205,6 +206,18 @@ private extension SettingViewModel {
205206
return total
206207
}
207208

209+
private func beginLoading(_ mode: LoadingState.Mode) {
210+
loadingState.begin(mode: mode) { [weak self] isLoading in
211+
self?.send(.setLoading(isLoading))
212+
}
213+
}
214+
215+
private func endLoading(_ mode: LoadingState.Mode) {
216+
loadingState.end(mode: mode) { [weak self] isLoading in
217+
self?.send(.setLoading(isLoading))
218+
}
219+
}
220+
208221
private func clearCacheDirectory() throws {
209222
let cachesDir = try FileManager.default.url(
210223
for: .cachesDirectory,

0 commit comments

Comments
 (0)