iOS

[아키텍처] Clean Architecture + MVVM

양밀루 2025. 6. 13. 19:52

1. Clean Architecture 기본 개념

  • 3개 레이어: Presentation (UI 로직) →  Domain (비즈니스 로직)   Data (데이터 접근)
  • 의존성 규칙: 내부 레이어는 외부 레이어에 의존하지 않음
  • 핵심: 비즈니스 로직을 UI/DB로부터 완전히 독립시키는 것

2. Domain Layer 상세 분석

// Domain Layer 구성
📁 Entities (비즈니스 모델)
  - Movie.swift
  - MovieQuery.swift

📁 UseCases (비즈니스 로직)
  - SearchMoviesUseCase.swift
  - FetchRecentMovieQueriesUseCase.swift

📁 Interfaces/Repositories (의존성 역전)
  - MoviesRepository.swift (Protocol)
  - MoviesQueriesRepository.swift (Protocol)

UseCase의 역할:

  • ✅ 비즈니스 규칙 구현 (검색 성공시 검색어 저장 등)
  • ✅ 데이터 검증 및 변환
  • ✅ 여러 Repository 조합
  • ❌ 데이터 가져오기는 Repository가 담당

왜 Result<[Entity], Error>를 사용하나?

  • Swift 표준 비동기 에러 처리 패턴
  • 성공/실패를 명확히 구분
  • 컴파일 타임 안전성 제공

3. Presentation Layer (MVVM)

// 역할 분담
🧠 ViewModel: "무엇을 보여줄지" 결정
  - UI 상태 관리 (로딩, 에러, 데이터)
  - 비즈니스 로직 연결 (UseCase 호출)
  - UIKit 독립적

🎮 ViewController: "어떻게 연결할지" 결정
  - View ↔ ViewModel 바인딩
  - 사용자 입력을 ViewModel에 전달
  - ViewModel 상태 변화를 UI에 반영

📱 View: "어떻게 보여줄지" 결정
  - 순수한 데이터 표시
  - 기본적인 UI 상호작용

데이터 흐름:

사용자 입력 → ViewController → ViewModel → UseCase → Repository → API
결과 반환 ← ViewController ← ViewModel ← UseCase ← Repository ← API

4. Data Layer - DTO의 필요성

왜 DTO가 필요한가?

  • API 응답 구조 ≠ 앱의 비즈니스 모델
  • 데이터 변환과 검증 역할
// API 응답 (DTO)
{
  "id": 550,
  "genre_ids": [18, 53, 35],
  "release_date": "1999-10-15"
}

// 앱 모델 (Entity)
Movie(
  id: "550",           // Int → String
  genre: .drama,       // [18,53,35] → Genre enum
  releaseDate: Date()  // "1999-10-15" → Date
)

DTO의 장점:

  • API 변경에 대한 격리
  • 불필요한 데이터 제거
  • 타입 안전성 보장

5. Clean Architecture의 단점

성능 문제:

  • 메모리 사용량 증가
  • 데이터 4번 변환 (JSON → DTO → Entity → ViewModel → UI)

복잡성 문제:

  • 간단한 기능도 7-10개 파일 필요
  • 높은 학습 곡선
  • 프로토타입/MVP에 부적합

작은 프로젝트엔 적합하지 않음

6. Method Dispatch와 성능

  • Static (가장 빠름): 컴파일 타임 결정, 인라이닝 가능
  • Dynamic (느림): 런타임 V-Table 룩업

Clean Architecture의 성능 이슈:

// 🐌 Protocol = Dynamic Dispatch (3.75배 느림)
let useCase: SearchMoviesUseCase = DefaultSearchMoviesUseCase()
useCase.execute() // 런타임에 실제 구현체 찾아야 함

// 🚀 Concrete Type = Static Dispatch
let useCase = DefaultSearchMoviesUseCase()
useCase.execute() // 컴파일 타임에 결정

성능 최적화 방법:

// ✅ final 키워드
final class DefaultRepository: MoviesRepository { }

// ✅ private 메서드 (항상 Static)
private func internalLogic() { }

// ✅ Struct 사용
struct ValueTypeService { }

 

    성능 최적화 부분은 관련 떱떱디씨 영상과 함께 추후에 자세히 정리 예정..!

 

https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

 

Clean Architecture and MVVM on iOS

When we develop software it is important to not only use design patterns, but also architectural patterns. There are many different…

tech.olx.com