| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 키노
- SOPT
- SWIFT
- 자바공부
- 영화일기
- 오블완
- 티스토리챌린지
- 백준
- 영화기록
- 플러터
- 리뷰
- 새벽녘 소소한 기록
- IOS
- 독서일기
- 영화리뷰
- 자바 스터디
- MVVM-C
- Flutter Toy Project
- sopt 35기
- 코딩공부
- sopt ios
- 영화
- java
- 자바
- 토이프로젝트
- 일기
- 프로그래머스
- swift concurrency
- Flutter
- 영화후기
- Today
- Total
새벽의 기록
[iOS] SwiftUI+MVVM-C 프로젝트 클린 아키텍처 도입 본문
iOS 프로젝트를 진행하면서 처음에는 MVVM-C(Model-View-ViewModel, Coordinator) 패턴을 적용했다.
MVVM은 뷰와 상태를 분리하는 데 효과적이지만, 프로젝트가 커질수록 몇 가지 한계가 드러났다.
이 글에서는 내가 경험한 문제점과, 이를 해결하기 위해 클린 아키텍처를 도입한 이유를 공유하고자 한다.
https://github.com/cerdeuk/CERTI-iOS
GitHub - cerdeuk/CERTI-iOS
Contribute to cerdeuk/CERTI-iOS development by creating an account on GitHub.
github.com
1. ViewModel 비대화 문제
MVVM만 사용했을 때 가장 크게 느낀 한계는 ViewModel이 지나치게 비대해진다는 점이다.
예를 들어, HomeViewModel에는 아래와 같은 메서드들이 있었다.
- getUserInfo() : 사용자 정보 불러오기
- getRecommendCertificationList() : 추천 자격증 가져오기
- deletePreCertification(id:) : 취득 예정 자격증 삭제
- toggleFavoriteCertification(id:) : 즐겨찾기 상태 토글
이 메서드들은 단순한 UI 상태 업데이트가 아니라, 앱의 핵심 문제 영역(도메인 로직)을 직접 다루고 있다.
즉, UI 로직과 도메인 로직이 한 클래스에 뒤섞여 있는 구조였고, 그 결과로 다음과 같은 문제가 발생했다.
- ViewModel 파일이 점점 길어지고 복잡해짐 (Massive ViewModel)
- 테스트하기 어려움 (네트워크와 UI 상태가 얽혀 있음)
- 다른 화면에서 재사용하기 어려움
- UI 변화와 관련 없는 핵심 로직까지 ViewModel에 들어옴
ViewModel은 View의 상태 관리와 뷰 반응 처리에 집중해야 하지만, 실제로는 도메인/비즈니스 로직까지 떠안고 있었던 것이다.
2. 의존성이 외부에 드러남
기존 구조에서는 NetworkService.shared.XXXService와 같이 ViewModel이 직접 싱글톤 방식으로 네트워크 계층을 호출했다.
이 방식의 문제는 다음과 같다.
- 유닛 테스트 작성 시 실제 네트워크 호출을 피하려면 Mocking이 번거롭다.
- 네트워크 계층 구조를 바꾸려면 ViewModel부터 손대야 한다.
즉, ViewModel이 외부 서비스에 강하게 의존하고 있었기 때문에 구조적 유연성이 떨어졌다.
3. 재사용성 부족
"추천 자격증 가져오기" 로직같은 경우에는 다른 화면에서도 충분히 필요할 수 있다.
그런데 이 로직이 ViewModel 안에 있으면 재사용하지 못하고, 결국 비슷한 코드를 다시 작성해야 했다.
이러한 문제들을 해결하기 위해 클린 아키텍처(Clean Architecture)를 도입했다.
Presentation (SwiftUI View + ViewModel)
↕ (ViewModel -> UseCase 호출)
Domain (UseCase, Entity, Protocols)
↕ (UseCase -> Repository 프로토콜 호출)
Data (Repository 구현, DTO, NetworkService)
- ViewModel : 상태 저장, UI 반응 처리
- UseCase : 도메인 로직 담당 (예: GetUserInfoUseCase, ToggleFavoriteUseCase)
- Repository : 데이터 접근 추상화 (네트워크, 로컬 저장소 등)
이 구조를 따르면서, ViewModel 내부에 있던 도메인 로직이 자연스럽게 UseCase로 이동했다.
도입 후
- ViewModel의 경량화
- ViewModel은 상태 관리에만 집중할 수 있어 훨씬 깔끔해졌다.
- 재사용성 향상
- 예를 들어 GetRecommendCertificationUseCase는 다른 화면에서도 그대로 재사용할 수 있다.
- 테스트 용이성 증가
- Repository를 프로토콜로 추상화했기 때문에, 테스트 시 FakeRepository나 MockRepository를 쉽게 주입할 수 있다.
- 유연한 의존성 구조
- 네트워크 레이어를 바꾸더라도 (예: Moya → Alamofire 교체), UseCase나 ViewModel에는 영향을 주지 않는다.
MVVM만으로도 충분히 앱을 개발할 수는 있지만, 서비스가 커지고 기능이 늘어날수록 유지보수 난이도가 급격히 올라갔다.
그래서 뷰와 핵심 로직을 구분하고, 테스트 가능성과 확장성을 확보하기 위해 클린 아키텍처를 도입했다.
물론 계층 분리가 지나치면 파일이 많아져 개발 속도가 떨어질 수도 있다.
하지만 관심사 분리와 의존성 역전이라는 원칙 덕분에, 장기적으로 훨씬 안정적이고 재사용 가능한 구조를 갖출 수 있었다.
'[iOS]' 카테고리의 다른 글
| [iOS] SwiftUI에서 MVVM-C 패턴 톺아보기 (0) | 2025.10.02 |
|---|---|
| [iOS] 클린아키텍처 공부할 때 주저리주저리 썼던 거 (0) | 2025.10.01 |
| [iOS] CoreData vs SwiftData vs Realm? 로컬 데이터베이스 비교 (0) | 2025.09.12 |
| [iOS] 화면 위 아래가 안 보이는 버그 black screen (0) | 2025.09.06 |
| [iOS] 기존 프로젝트에 tuist 적용하기 (0) | 2025.09.06 |
