🤖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<Task>


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<[Task], Never>

}


public final class FetchTasksUseCaseImp: FetchTasksUseCase {

private let repository: TaskRepository


public init(repository: TaskRepository) {

self.repository = repository

}


public func execute() -> AnyPublisher<[Task], Never> {

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<AnyCancellable> = []


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! 🚀

다음 내용이 궁금하다면?

또는

이미 회원이신가요?

2024년 7월 9일 오전 2:02

댓글 0