๐คSwiftUI ํด๋ฆฐ ์ํคํ ์ฒ์ SwiftData ์ ์ฉํ๊ธฐ
## ํด๋ฆฐ ์ํคํ ์ฒ + SwiftUI์์ SwiftData๋ ์ด๋ป๊ฒ ๊ตฌํ์ด ๋ผ์ผ ํ ๊น? ์๋ ํ์ธ์! ์ค๋์ SwiftUI์ SwiftData๋ฅผ ํ์ฉํด ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐํด ๋ณด๋ ค๊ณ ํฉ๋๋ค. iOS ๊ฐ๋ฐ์๋ก์ UI์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ง ๊ณ ๋ฏผ์ด ๋ง์ผ์์ฃ ? ๊ทธ๋ ๋ค๋ฉด ์ด๋ฒ ํฌ์คํธ๋ฅผ ํตํด ๋ค์ํ Tips์ ์ป์ด๋ณด์ธ์. ### ํด๋ฆฐ ์ํคํ ์ฒ๋? ํด๋ฆฐ ์ํคํ ์ฒ(Clean Architecture)๋ ์ํํธ์จ์ด ์ํคํ ์ฒ ํจํด ์ค ํ๋๋ก, ๊ฐ ๋ ์ด์ด๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋๋ก ๋ง๋ค์ด ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ๊ณ ํ ์คํธ๊ฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐ ๋ชฉํ๋ฅผ ๋๊ณ ์์ต๋๋ค. - ๋ทฐ(View) : ์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI)๋ฅผ ๋ด๋นํฉ๋๋ค. - ํ๋ ์ ํ ์ด์ (Presentation) : UI ๋ก์ง์ ์ฒ๋ฆฌํ๊ณ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ค๋นํฉ๋๋ค. - ๋๋ฉ์ธ(Domain) : ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํฉ๋๋ค. - ๋ฐ์ดํฐ(Data) : ๋ฐ์ดํฐ ์์ค์์ ํต์ ์ ๋ด๋นํฉ๋๋ค. ### SwiftData๋? SwiftData๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด Apple์์ ์ ๊ณตํ๋ ํ๋ ์์ํฌ์ ๋๋ค. Core Data๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๊ณ ์์ง๋ง, ๋ ํ๋์ ์ด๊ณ Swift์ ์ ํฉํ ๋ฌธ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ๋ฐ์ดํฐ ๋ชจ๋ธ์ ์ ์ํ๊ณ , ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ , ๊ฐ์ ธ์ค๋ ์์ ์ ํ ์ ์์ต๋๋ค. ### SwiftData๋ฅผ ์ฌ์ฉํ ํด๋ฆฐ ์ํคํ ์ฒ ๊ตฌํ SwiftData์ ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๊ฒฐํฉํ์ฌ iOS ์ฑ์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋จ๊ณ๋ณ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค. #### 1. ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ์ ๋จผ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ชจ๋ธ๋ค์ ์ ์ํฉ๋๋ค. SwiftData๋ฅผ ์ฌ์ฉํ๋ฉด @Model ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด ๊ฐ๋จํ ๋ชจ๋ธ์ ๋ง๋ค ์ ์์ต๋๋ค. ```swift import SwiftData @Model public final class Task { @Attribute var title: String @Attribute var isCompleted: Bool @Attribute var createdAt: Date init(title: String, isCompleted: Bool, createdAt: Date) { self.title = title self.isCompleted = isCompleted self.createdAt = createdAt } } ``` ์ด๋ ๊ฒ ํ๋ฉด title , isCompleted , createdAt ํ๋กํผํฐ๋ฅผ ๊ฐ์ง Task ๊ฐ์ฒด๊ฐ ์์ฑ๋ฉ๋๋ค. SwiftData์ @Model ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๋์ด ์ด๋ฃจ์ด์ง๋๋ค. #### 2. ๋ฆฌํฌ์งํ ๋ฆฌ ์์ฑ ๋ฆฌํฌ์งํ ๋ฆฌ๋ ๋ฐ์ดํฐ ์์ค์ ์ํธ ์์ฉํ๋ ์ฝ๋๋ฅผ ํฌํจํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์์กด์ฑ์ ์ต์ํํ๊ณ , ๋ฐ์ดํฐ ์ก์ธ์ค๋ฅผ ์บก์ํํ ์ ์์ต๋๋ค. ```swift import SwiftData import SwiftUI public protocol TaskRepository { func fetchTasks() -> [Task] func addTask(_ task: Task) func deleteTask(_ task: Task) } public final class TaskRepositoryImp: TaskRepository { @Container private var container: DataContainer public func fetchTasks() -> [Task] { return container.fetch() } public func addTask(_ task: Task) { container.save(task) } public func deleteTask(_ task: Task) { container.delete(task) } } ``` ์ด ์ฝ๋๋ TaskRepository ๋ผ๋ ํ๋กํ ์ฝ์ ์ ์ํ๊ณ , ์ด๋ฅผ ๊ตฌํํ TaskRepositoryImp ํด๋์ค๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ณต์กํ ๋ก์ง์ ์บก์ํํฉ๋๋ค. #### 3. ์ ์ค์ผ์ด์ค ์ ์ ์ ์ค์ผ์ด์ค๋ ๋๋ฉ์ธ ๋ก์ง์ ๋ถ๋ฆฌํ์ฌ ์ฌ์ฉ์์ ์์ฒญ์ ๋ฐ๋ผ ํน์ ์์ ์ ์ํํฉ๋๋ค. ```swift import Combine public protocol FetchTasksUseCase { func execute() -> AnyPublisher } public final class FetchTasksUseCaseImp: FetchTasksUseCase { private let repository: TaskRepository public init(repository: TaskRepository) { self.repository = repository } public func execute() -> AnyPublisher { return Just(repository.fetchTasks()) .eraseToAnyPublisher() } } ``` ์ ์ฝ๋๋ FetchTasksUseCase ํ๋กํ ์ฝ๊ณผ ๊ทธ ๊ตฌํ์ฒด๋ฅผ ์ ์ํฉ๋๋ค. execute ๋ฉ์๋๋ ์ ์ฅ๋ Task ๋ฆฌ์คํธ๋ฅผ ๋ฐํํฉ๋๋ค. #### 4. ViewModel ์์ฑ ViewModel์ ์ ์ค์ผ์ด์ค๋ฅผ ํตํด ๋ฐ์ดํฐ์ UI๋ฅผ ์ฐ๊ฒฐํ๋ ์ญํ ์ ํฉ๋๋ค. ```swift import Combine public final class TaskViewModel: ObservableObject { @Published var tasks: [Task] = [] private let fetchTasksUseCase: FetchTasksUseCase private var cancellables: Set = [] public init(fetchTasksUseCase: FetchTasksUseCase) { self.fetchTasksUseCase = fetchTasksUseCase fetchTasks() } public func fetchTasks() { fetchTasksUseCase.execute() .sink { [weak self] tasks in self?.tasks = tasks } .store(in: &cancellables) } } ``` ViewModel์ @Published ๋ฅผ ์ฌ์ฉํด UI์ ๋ฐ์ธ๋ฉํ ๋ฐ์ดํฐ๋ฅผ ์ ์ธํ๊ณ , ์ ์ค์ผ์ด์ค๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. #### 5. SwiftUI View ๋ง๋ค๊ธฐ ์ต์ข ์ ์ผ๋ก SwiftUI View๋ฅผ ์์ฑํด ๋ฐ์ดํฐ๋ฅผ ํ์ํฉ๋๋ค. ```swift import SwiftUI struct TaskListView: View { @ObservedObject var viewModel: TaskViewModel var body: some View { List(viewModel.tasks) { task in TaskRow(task: task) } .onAppear { viewModel.fetchTasks() } } } struct TaskRow: View { var task: Task var body: some View { HStack { Text(task.title) Spacer() if task.isCompleted { Image(systemName: "checkmark") } } } } ``` ViewModel์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ SwiftUI์ List๋ฅผ ํตํด ํ๋ฉด์ ํ์ํฉ๋๋ค. ### ์์ฝ ์ด๋ฒ ํฌ์คํธ์์๋ SwiftUI์ SwiftData๋ฅผ ์ฌ์ฉํด ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ดํด๋ณด์์ต๋๋ค. ๊ฐ ๋ ์ด์ด๋ฅผ ๋ถ๋ฆฌํ์ฌ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ ์ง๋ณด์ํ๋ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ ํ์ธํ ์ ์์์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ํตํด ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ฌ๋ณด์ธ์! ๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ์ด์ผ๊ธฐํ๊ณ ์ถ์ ์ฃผ์ ๊ฐ ์๋ค๋ฉด ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์. Happy Coding! ๐