UIKit 기반 프로젝트에서 MVVM 아키텍처를 활용하면서 ViewModel이 점점 비대해지는 문제와 ViewController에서 직접 화면 전환을 수행하는 비효율적인 구조를 경험했다.
이러한 문제를 해결하기 위해 Clean Architecture를 적용하여 역할을 명확히 분리하고, Coordinator 패턴을 도입하여 화면 전환을 보다 효율적으로 관리하는 방식으로 설계를 개선하였다.
이 글에서는 MVVM-C 패턴과 Clean Architecture를 결합하여 설계한 과정과 고려한 점을 공유하고자 한다.
1️⃣ 기존 MVVM 아키텍처에서의 문제점
- ViewModel의 점진적 비대화 문제
- 기능과 로직이 추가될수록 ViewModel이 점점 커지면서 여러 역할을 동시에 담당
- ViewModel이 비즈니스 로직 처리, 데이터 변환, 네트워크 요청까지 포함하면서 가독성과 유지보수성이 저하
- 따라서, 각 계층을 보다 명확히 분리하여 역할을 모듈화할 필요가 있음
- 비효율적인 화면 전환 구조
- ViewController에서 사용자 입력을 받아 ViewModel로 전달하고, ViewModel에서 화면 전환을 위한 Output을 ViewController로 전달하는 흐름은 적절해 보였다.
- 그러나 ViewController가 직접 NavigationController에 접근하여 화면 전환을 수행하는 구조는 직관적이지 않았다.
- 특히, NavigationController는 ViewController보다 상위 계층에 존재하는데, 하위 모듈(ViewController)이 직접 상위 모듈(NavigationController)에 접근하여 화면 전환을 명령하는 방식은 적절하지 않다고 판단하였다.
이러한 문제를 해결하기 위해 Clean Architecture를 도입하여 역할을 명확히 분리하고,
Coordinator 패턴을 적용하여 화면 전환을 개선하는 방향으로 설계를 변경하였다.
2️⃣ Clean Architecture + MVVM-C 패턴 설계
Clean Architecture와 MVVM 패턴을 결합하여 역할을 명확히 분리하였다.
📌 Presentation Layer (UI & ViewModel)
- ViewController: UI를 담당하며, 사용자의 액션을 처리하고 ViewModel과 통신을 담당
- ViewModel: ViewController에서 전달받은 Input을 처리하고, Output을 전달한다. 직접적인 비즈니스 로직 처리는 수행하지 않고, UseCase를 호출하여 처리
📌 Domain Layer (비즈니스 로직 처리)
- UseCase: ViewModel에서 받은 요청을 처리하는 역할, 도메인 로직을 실행하며, Repository를 통해 데이터를 요청한다.
📌 Data Layer (데이터 관리 및 네트워크 처리)
- Repository: API 요청을 담당하는 역할, Query 데이터를 받아 RequestDTO로 변환한 후, NetworkRouter를 통해 NetworkManager에 요청을 전달 및 반환받은 ResponseDTO를 Entity 데이터로 변환하여 반환
- NetworkRouter: API의 엔드포인트를 구성하는 역할, 전달받은 RequestDTO를 통해 Method, Parameter, Header를 조합
- NetworkManager: HTTP 네트워크 요청을 수행하는 역할, API 요청을 실행하고 데이터를 반환
이러한 분리를 통해 각 계층이 담당하는 역할을 명확히 하고, 응집도를 높이고 결합도를 낮추어 유지보수성을 높였다.
3️⃣ 의존성 관리 및 주입 (DI⋅DIP 적용)
- DI(Dependency Injection) 적용
각 객체는 의존성을 직접 생성하는 것이 아니라, 생성자 메서드를 통해 외부에서 주입받도록 설계
- 모든 객체는 역할에 따라 분리하고, Protocol을 활용하여 인터페이스를 정의
- 구현체가 아닌 추상화된 Protocol에 의존하도록 하여 DIP(Dependency Inversion Principle)를 준수
- 팩토리 메서드 패턴(Factory Method Pattern)을 활용하여 ViewController를 생성하면서 ViewModel을 함께 의존성을 주입하는 방식
- 전역에서 사용되는 객체는 싱글톤 패턴을 활용하여 하나의 인스턴스를 공유
- 의존성 관리 방식의 효과
- 객체 간 결합도를 낮추어 확장성을 확보
- 테스트가 용이한 구조를 구축하여 Unit Test 및 Mock 객체 활용이 가능
- 각 계층이 독립적으로 동작할 수 있도록 설계하여, 유지보수성을 강화
4️⃣ 화면 전환 개선 – Coordinator 패턴 적용
- 기존 문제점 해결
- 기존 방식에서는 ViewController가 직접 NavigationController에 접근하여 화면 전환을 수행하였다.
- 이 방식은 ViewController가 화면 전환 로직을 직접 관리해야 하므로, ViewController의 책임이 증가하고 코드 복잡성이 높아지는 문제가 발생하였다.
- Coordinator 패턴을 활용한 해결
- 각 Coordinator가 navigationController를 직접 관리하도록 설계하여, 화면 전환 책임을 분리하였다.
- 앱의 루트에는 AppCoordinator가 존재하고, 각 탭에는 독립적인 Coordinator를 구성하여 화면 전환을 담당하도록 하였다.
- Coordinator가 Delegate 패턴을 활용하여 화면 전환을 처리하도록 설계하였다.
- AppCoordinator는 childCoordinator를 Coordinator 타입의 배열로 관리하며, 루트 뷰 변경이 필요할 경우 이를 활용하도록 하였다.
- 개선된 화면 전환 흐름
- ViewController에서 사용자의 Input을 ViewModel로 전달
- ViewModel이 이를 감지하여 ViewController로 Output을 전달
- ViewController는 Coordinator에 화면 전환을 요청
- Coordinator는 NavigationController를 활용하여 화면 전환을 수행
이렇게 함으로써 ViewController는 UI 처리에 집중할 수 있고, 화면 전환 로직은 Coordinator에서 일괄적으로 관리하는
방식으로 개선되었다.
5️⃣ 결론 – Clean Architecture + MVVM-C 적용을 통해 얻은 개선점
- ViewModel의 역할을 명확히 분리하여, 비대해지는 문제를 해결
- Clean Architecture를 적용하여 계층 간 역할을 분리하고, 유지보수성을 향상
- Coordinator 패턴을 적용하여 ViewController가 직접 Navigation을 관리하지 않도록 개선
- DI 및 DIP를 활용하여 확장 가능하고 테스트하기 쉬운 구조를 설계
이번 설계를 통해 UIKit에서 MVVM-C와 Clean Architecture를 효과적으로 결합할 수 있는 방법을 고민하고,
유지보수성과 확장성을 고려한 아키텍처를 구축할 수 있었다.
앞으로도 프로젝트의 특성과 요구사항에 맞는 아키텍처를 지속적으로 탐색하고 최적화해 나갈 계획이다.
'프로젝트 > LookForRealBurger' 카테고리의 다른 글
Secure Enclave와 Keychain을 활용한 Refresh Token 보안 관리 (0) | 2025.01.06 |
---|---|
Unit Test - Mock 객체 메서드 내 분기처리 (0) | 2024.12.30 |
ViewController의 Lifecycle과 Modal Present 이슈 (0) | 2024.12.30 |
Pull To Refresh - 당겨서 새로고침 UX 개선 (0) | 2024.12.29 |
'LookForRealBurger' 라는 주제로 LSLP 를 진행하고 난 후 (3) | 2024.09.08 |