From 057bb86e5d8b6e84b1d637ec4391183fe08be383 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 22:54:30 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=B7=B0=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=9D=98=20State=EC=97=90=20equatable=EC=9D=84=20=EC=B1=84?= =?UTF-8?q?=ED=83=9D=ED=95=98=EC=97=AC=20=EC=83=81=ED=83=9C=EA=B0=80=20?= =?UTF-8?q?=EB=B3=80=ED=95=98=EC=A7=80=20=EC=95=8A=EC=95=98=EC=9D=84=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Domain/Entity/TodoQuery.swift | 2 +- .../ViewModel/AccountViewModel.swift | 4 +- .../ViewModel/LoginViewModel.swift | 4 +- .../ViewModel/ProfileViewModel.swift | 4 +- .../PushNotificationListViewModel.swift | 22 +++++----- .../PushNotificationSettingsViewModel.swift | 4 +- .../ViewModel/RootViewModel.swift | 4 +- .../ViewModel/SearchViewModel.swift | 4 +- .../ViewModel/SettingViewModel.swift | 4 +- .../ViewModel/TodoDetailViewModel.swift | 4 +- .../ViewModel/TodoEditorViewModel.swift | 6 +-- .../ViewModel/TodoListViewModel.swift | 40 +++++++++---------- .../ViewModel/TodoManageViewModel.swift | 4 +- 13 files changed, 53 insertions(+), 53 deletions(-) diff --git a/DevLog/Domain/Entity/TodoQuery.swift b/DevLog/Domain/Entity/TodoQuery.swift index 5b2e80be..10689a86 100644 --- a/DevLog/Domain/Entity/TodoQuery.swift +++ b/DevLog/Domain/Entity/TodoQuery.swift @@ -7,7 +7,7 @@ import Foundation -struct TodoQuery { +struct TodoQuery: Equatable { enum SortTarget: Equatable, Hashable { case createdAt case updatedAt diff --git a/DevLog/Presentation/ViewModel/AccountViewModel.swift b/DevLog/Presentation/ViewModel/AccountViewModel.swift index 9f20208d..8d80560f 100644 --- a/DevLog/Presentation/ViewModel/AccountViewModel.swift +++ b/DevLog/Presentation/ViewModel/AccountViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class AccountViewModel: Store { - struct State { + struct State: Equatable { var currentProvider: AuthProvider? var connectedProviders: [AuthProvider] = [] var disconnectedProviders: [AuthProvider] = [] @@ -87,7 +87,7 @@ final class AccountViewModel: Store { .filter { !allProviders.contains($0) } } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/LoginViewModel.swift b/DevLog/Presentation/ViewModel/LoginViewModel.swift index 2dce8f01..7f192bda 100644 --- a/DevLog/Presentation/ViewModel/LoginViewModel.swift +++ b/DevLog/Presentation/ViewModel/LoginViewModel.swift @@ -12,7 +12,7 @@ import GoogleSignIn @Observable final class LoginViewModel: Store { - struct State { + struct State: Equatable { var signIn: Bool? var isLoading = false var showAlert: Bool = false @@ -76,7 +76,7 @@ final class LoginViewModel: Store { state.signIn = result } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/ProfileViewModel.swift b/DevLog/Presentation/ViewModel/ProfileViewModel.swift index 8b551805..8852e9ba 100644 --- a/DevLog/Presentation/ViewModel/ProfileViewModel.swift +++ b/DevLog/Presentation/ViewModel/ProfileViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class ProfileViewModel: Store { - struct State { + struct State: Equatable { var name: String = "" var email: String = "" var statusMessage: String = "" @@ -185,7 +185,7 @@ final class ProfileViewModel: Store { case .updateStatusTextFieldFocus(let focused): state.showDoneButton = focused } - self.state = state + if self.state != state { self.state = state } return effects } // swiftlint:enable cyclomatic_complexity diff --git a/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift b/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift index 24a12680..1845ad81 100644 --- a/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift +++ b/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class PushNotificationListViewModel: Store { - struct State { + struct State: Equatable { var notifications: [PushNotificationItem] = [] var showAlert: Bool = false var showToast: Bool = false @@ -19,7 +19,6 @@ final class PushNotificationListViewModel: Store { var isLoading: Bool = false var hasMore: Bool = false var nextCursor: PushNotificationCursor? - var pendingTask: (PushNotificationItem, Int)? var query: PushNotificationQuery var selectedTodoID: TodoIDItem? } @@ -57,6 +56,7 @@ final class PushNotificationListViewModel: Store { private let toggleReadUseCase: TogglePushNotificationReadUseCase private let fetchQueryUseCase: FetchPushNotificationQueryUseCase private let updateQueryUseCase: UpdatePushNotificationQueryUseCase + private var pendingTask: (PushNotificationItem, Int)? init( fetchUseCase: FetchPushNotificationsUseCase, @@ -99,7 +99,7 @@ final class PushNotificationListViewModel: Store { effects = reduceByRun(action, state: &state) } - self.state = state + if self.state != state { self.state = state } return effects } @@ -158,12 +158,12 @@ private extension PushNotificationListViewModel { switch action { case .deleteNotification(let item): var effects: [SideEffect] = [] - if let (pendingItem, _) = state.pendingTask { + if let (pendingItem, _) = pendingTask { effects = [.delete(pendingItem)] } if let index = state.notifications.firstIndex(where: { $0.id == item.id }) { - state.pendingTask = (item, index) + pendingTask = (item, index) state.notifications.remove(at: index) setToast(&state, isPresented: true) } @@ -175,9 +175,9 @@ private extension PushNotificationListViewModel { return [.toggleRead(item.todoID)] } case .undoDelete: - guard let (item, index) = state.pendingTask else { return [] } + guard let (item, index) = pendingTask else { return [] } state.notifications.insert(item, at: index) - state.pendingTask = nil + pendingTask = nil case .setAlert(let isPresented): setAlert(&state, isPresented: isPresented) case .toggleSortOption: @@ -218,11 +218,11 @@ private extension PushNotificationListViewModel { state.nextCursor = nil return [.fetchNotifications(state.query, cursor: nil)] case .loadNextPage: - guard state.hasMore, !state.isLoading, state.pendingTask == nil else { return [] } + guard state.hasMore, !state.isLoading, pendingTask == nil else { return [] } return [.fetchNotifications(state.query, cursor: state.nextCursor)] case .confirmDelete: - guard let (item, _) = state.pendingTask else { return [] } - state.pendingTask = nil + guard let (item, _) = pendingTask else { return [] } + pendingTask = nil return [.delete(item)] case .setToast(let isPresented): setToast(&state, isPresented: isPresented) @@ -245,7 +245,7 @@ private extension PushNotificationListViewModel { state.nextCursor = nil case .appendNotifications(let notifications, let nextCursor): let filteredNotifications: [PushNotificationItem] - if let (pendingItem, _) = state.pendingTask { + if let (pendingItem, _) = pendingTask { filteredNotifications = notifications.filter { $0.id != pendingItem.id } } else { filteredNotifications = notifications diff --git a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift index 8f394629..870ea7f5 100644 --- a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift +++ b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class PushNotificationSettingsViewModel: Store { - struct State { + struct State: Equatable { var pushNotificationEnable: Bool = false var viewPushNotificationTime: Date = .init() var sheetPushNotificationTime: Date = .init() @@ -99,7 +99,7 @@ final class PushNotificationSettingsViewModel: Store { state.showTimePicker = false state.sheetPushNotificationTime = state.viewPushNotificationTime } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/RootViewModel.swift b/DevLog/Presentation/ViewModel/RootViewModel.swift index 1d3c2eae..1f9b09b5 100644 --- a/DevLog/Presentation/ViewModel/RootViewModel.swift +++ b/DevLog/Presentation/ViewModel/RootViewModel.swift @@ -10,7 +10,7 @@ import Combine @Observable final class RootViewModel: Store { - struct State { + struct State: Equatable { var showAlert: Bool = false var alertTitle: String = "" var alertMessage: String = "" @@ -89,7 +89,7 @@ final class RootViewModel: Store { state.signIn = result } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/SearchViewModel.swift b/DevLog/Presentation/ViewModel/SearchViewModel.swift index f8910042..25f3791d 100644 --- a/DevLog/Presentation/ViewModel/SearchViewModel.swift +++ b/DevLog/Presentation/ViewModel/SearchViewModel.swift @@ -10,7 +10,7 @@ import OrderedCollections @Observable final class SearchViewModel: Store { - struct State { + struct State: Equatable { var isLoading: Bool = false var isSearching: Bool = false var searchQuery: String = "" @@ -124,7 +124,7 @@ final class SearchViewModel: Store { state.showAllWebPages = shouldShowAll } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/SettingViewModel.swift b/DevLog/Presentation/ViewModel/SettingViewModel.swift index e99c1dd1..11cf264b 100644 --- a/DevLog/Presentation/ViewModel/SettingViewModel.swift +++ b/DevLog/Presentation/ViewModel/SettingViewModel.swift @@ -10,7 +10,7 @@ import Combine @Observable final class SettingViewModel: Store { - struct State { + struct State: Equatable { var theme: SystemTheme = .automatic var dirSize: Int64 = 0 var isLoading = false @@ -95,7 +95,7 @@ final class SettingViewModel: Store { } } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift index d1d8e15c..5a9df4d6 100644 --- a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class TodoDetailViewModel: Store { - struct State { + struct State: Equatable { var todo: Todo? var isLoading: Bool = false var showAlert: Bool = false @@ -70,7 +70,7 @@ final class TodoDetailViewModel: Store { effects = [.upsertTodo(todo)] } - self.state = state + if self.state != state { self.state = state } return effects } diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index b2d6a8cc..0d215c3c 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -10,14 +10,14 @@ import OrderedCollections @Observable final class TodoEditorViewModel: Store { - struct State { + struct State: Equatable { var title: String = "" var content: String = "" var dueDate: Date? var tags: OrderedSet = [] var tagText: String = "" var focusOnEditor: Bool = false - var hasDueDate: Bool { return dueDate != nil } + var hasDueDate: Bool { dueDate != nil } var tabViewTag: Tag = .editor var isValidToSave: Bool { !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && @@ -111,7 +111,7 @@ final class TodoEditorViewModel: Store { } } - self.state = state + if self.state != state { self.state = state } return [] } } diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index d27e22f5..f64a79de 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class TodoListViewModel: Store { - struct State { + struct State: Equatable { var todos: [TodoListItem] = [] var searchText: String = "" var searchResults: [TodoListItem] = [] @@ -25,8 +25,6 @@ final class TodoListViewModel: Store { var showToast: Bool = false var toastMessage: String = "" var hasMore: Bool = false - var nextCursor: TodoCursor? - var pendingTask: (TodoListItem, Int)? } enum Action { @@ -82,6 +80,8 @@ final class TodoListViewModel: Store { private let fetchTodoByIDUseCase: FetchTodoByIDUseCase private let upsertTodoUseCase: UpsertTodoUseCase private let deleteTodoUseCase: DeleteTodoUseCase + private var pendingTask: (TodoListItem, Int)? + private var nextCursor: TodoCursor? init( fetchTodosUseCase: FetchTodosUseCase, @@ -129,7 +129,7 @@ final class TodoListViewModel: Store { effects = reduceByRun(action, state: &state) } - self.state = state + if self.state != state { self.state = state } return effects } @@ -154,7 +154,7 @@ final class TodoListViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let page = try await fetchTodosUseCase.execute(state.query, cursor: state.nextCursor) + let page = try await fetchTodosUseCase.execute(state.query, cursor: nextCursor) send(.appendTodos(page.items.map { TodoListItem(from: $0) }, nextCursor: page.nextCursor)) let hasMore = page.items.count == state.query.pageSize && page.nextCursor != nil send(.setHasMore(hasMore)) @@ -239,12 +239,12 @@ private extension TodoListViewModel { state.showEditor = value case .swipeTodo(let todo): var effects: [SideEffect] = [] - if let (pendingItem, _) = state.pendingTask { + if let (pendingItem, _) = pendingTask { effects = [.delete(pendingItem.id)] } if let index = state.todos.firstIndex(where: { $0.id == todo.id }) { - state.pendingTask = (todo, index) + pendingTask = (todo, index) state.todos.remove(at: index) setToast(&state, isPresented: true) } @@ -252,23 +252,23 @@ private extension TodoListViewModel { return effects case .setSortTarget(let target): state.query.sortTarget = target - state.nextCursor = nil + self.nextCursor = nil return [.fetch] case .setSortOrder(let order): state.query.sortOrder = order - state.nextCursor = nil + self.nextCursor = nil return [.fetch] case .togglePinnedOnly: state.query.isPinned = state.query.isPinned == true ? nil : true - state.nextCursor = nil + self.nextCursor = nil return [.fetch] case .setCompletionFilter(let filter): state.query.completionFilter = filter - state.nextCursor = nil + self.nextCursor = nil return [.fetch] case .resetFilters: state.query = TodoQuery(kind: state.kind) - state.nextCursor = nil + self.nextCursor = nil return [.fetch] case .setIsSearching(let value): state.isSearching = value @@ -285,11 +285,11 @@ private extension TodoListViewModel { case .tapTogglePinned(let todo): return [.togglePinned(todo)] case .undoDelete: - guard let (todo, index) = state.pendingTask else { return [] } + guard let (todo, index) = pendingTask else { return [] } if index <= state.todos.count { state.todos.insert(todo, at: index) } - state.pendingTask = nil + pendingTask = nil default: break } @@ -299,15 +299,15 @@ private extension TodoListViewModel { func reduceByView(_ action: Action, state: inout State) -> [SideEffect] { switch action { case .confirmDelete: - guard let (item, _) = state.pendingTask else { + guard let (item, _) = pendingTask else { return [] } - state.pendingTask = nil + pendingTask = nil return [.delete(item.id)] case .onAppear: return [.fetch] case .loadNextPage: - guard state.hasMore, !state.isLoading, state.pendingTask == nil else { return [] } + guard state.hasMore, !state.isLoading, pendingTask == nil else { return [] } return [.loadNextPage] case .setSearchText(let text): state.searchText = text @@ -354,16 +354,16 @@ private extension TodoListViewModel { state.isLoading = value case .appendTodos(let todos, let nextCursor): let filteredTodos: [TodoListItem] - if let (pendingItem, _) = state.pendingTask { + if let (pendingItem, _) = pendingTask { filteredTodos = todos.filter { $0.id != pendingItem.id } } else { filteredTodos = todos } state.todos.append(contentsOf: filteredTodos) - state.nextCursor = nextCursor + self.nextCursor = nextCursor case .resetPagination: state.todos = [] - state.nextCursor = nil + self.nextCursor = nil state.hasMore = false case .setHasMore(let value): state.hasMore = value diff --git a/DevLog/Presentation/ViewModel/TodoManageViewModel.swift b/DevLog/Presentation/ViewModel/TodoManageViewModel.swift index 86b1abf5..1ea70690 100644 --- a/DevLog/Presentation/ViewModel/TodoManageViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoManageViewModel.swift @@ -9,7 +9,7 @@ import Foundation @Observable final class TodoManageViewModel: Store { - struct State { + struct State: Equatable { var todoKindPreferences: [TodoKindPreference] } @@ -38,7 +38,7 @@ final class TodoManageViewModel: Store { } } - self.state = state + if self.state != state { self.state = state } return [] } }