SwiftUI 의존성 그래프 아키텍처 (객체 수명 및 스코프)

발행: (2026년 1월 10일 오전 08:11 GMT+9)
6 min read
원문: Dev.to

I’m happy to help translate the article, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source link and all formatting exactly as you requested.

🧠 핵심 원칙

객체 수명을 설계하지 않으면 SwiftUI가 대신 설계하게 되고, 그 결과가 마음에 들지 않을 것입니다.

모든 객체는 다음을 가져야 합니다:

  • 명확한 소유자
  • 명확한 수명
  • 명확한 범위

🧱 1. 모델링해야 할 세 가지 수명

모든 의존성은 다음 카테고리 중 하나에 해당합니다:

1. App Lifetime

  • analytics
  • feature flags
  • auth session
  • configuration
  • logging

2. Feature Lifetime

  • ViewModels
  • repositories
  • coordinators
  • use cases

3. View Lifetime

  • ephemeral helpers
  • formatters
  • local state

이러한 수명을 혼합하면 메모리 누수와 버그가 발생합니다.

🧭 2. 의존성 그래프 레이어

레이어를 생각하세요:

AppContainer

FeatureContainer

ViewModel

View

데이터와 소유권은 하향으로만 흐르며, 위로 흐르는 것은 없어야 합니다.

🏗️ 3. 앱 컨테이너 (그래프의 루트)

final class AppContainer {
    let apiClient: APIClient
    let authService: AuthService
    let analytics: AnalyticsService
    let featureFlags: FeatureFlagService

    init() {
        self.apiClient = APIClient()
        self.authService = AuthService()
        self.analytics = AnalyticsService()
        self.featureFlags = FeatureFlagService()
    }
}
  • 한 번만 생성
  • 앱 전체 수명 동안 존재
  • 아래쪽으로 주입
  • 절대 이 인스턴스를 다시 생성하지 마세요.

📦 4. Feature Containers (Scoped Lifetimes)

각 기능은 자체 그래프를 구축합니다:

final class ProfileContainer {
    let repository: ProfileRepository
    let viewModel: ProfileViewModel

    init(app: AppContainer) {
        self.repository = ProfileRepository(api: app.apiClient)
        self.viewModel = ProfileViewModel(repo: repository)
    }
}
  • 기능이 나타날 때 생성됩니다
  • 기능이 사라질 때 파괴됩니다
  • 자신의 ViewModel을 소유합니다

이를 통해 깔끔한 정리를 할 수 있습니다.

🧩 5. ViewModels는 종속성을 구축하지 않음

잘못된 예:

class ProfileViewModel {
    let api = APIClient()
}

올바른 예:

class ProfileViewModel {
    let repo: ProfileRepository

    init(repo: ProfileRepository) {
        self.repo = repo
    }
}

ViewModels는 종속성을 사용합니다; 절대로 직접 생성하지 않습니다.

🧬 6. Environment as Graph Injector (Not Storage)

컨테이너를 개별 서비스가 아니라 환경을 통해 전달합니다:

.environment(\.profileContainer, container)

뷰에서 이를 가져옵니다:

@Environment(\.profileContainer) var container

뷰는 ViewModel 및 기타 의존성을 전역 상태에 의존하지 않고 받습니다.

🧱 7. Lifetime = Owner

If you can’t answer “Who deallocates this?” you have a bug.
→ “이것을 누가 해제하는가?” 라는 질문에 답할 수 없다면 버그가 있습니다.

Examples of ownership:
소유권의 예시:

  • AppContainer → app lifetime
    AppContainer → 앱 수명
  • FeatureContainer → navigation lifetime
    FeatureContainer → 내비게이션 수명
  • ViewModel → feature lifetime
    ViewModel → 피처 수명
  • View → frame lifetime
    View → 프레임 수명

Ownership must be visible in code.
소유권은 코드에 명시적으로 드러나야 합니다.

🔄 8. 탐색은 객체 수명을 정의한다

탐색은 UI뿐만 아니라 메모리 관리이다.

NavigationStack(path: $path) {
    FeatureEntry()
}

기능이 스택을 떠날 때:

  • 컨테이너가 해제됩니다
  • ViewModel이 해제됩니다
  • 구독이 취소됩니다
  • 작업이 중단됩니다

이러한 일이 발생하지 않으면 그래프가 잘못된 것입니다.

🧠 9. 싱글톤 회피 (편리함을 잃지 않으면서)

Instead of:

Analytics.shared.track()

Do:

analytics.track()

analyticsAppContainer에서 주입됩니다.

다음과 같은 이점을 유지합니다:

  • 전역과 유사한 접근
  • 테스트 가능성
  • 제어

진정한 전역 상태 없이.

🧪 10. 그래프 테스트

모든 것이 주입되었기 때문에 테스트는 간단합니다:

let mockRepo = MockProfileRepository()
let vm = ProfileViewModel(repo: mockRepo)

다음이 필요 없습니다:

  • 전역 스텁
  • 싱글톤 오버라이드
  • 시스템과 싸우기

당신의 의존성 그래프 테스트 하네스입니다.

❌ 11. Common Anti‑Patterns

Avoid:

  • ViewModel 내부에서 서비스 구축
  • 전역 정적 싱글톤
  • 모든 곳에 모든 것을 주입
  • 네비게이션보다 오래 살아남는 기능 컨테이너
  • 순환 의존성
  • 환경 객체를 서비스 로케이터로 사용

These lead to leaks, bugs, untestable code, and impossible refactors.

🧠 정신 모델

Who creates it?
Who owns it?
Who destroys it?

세 가지 질문에 모두 답할 수 있다면 그래프가 정상적인 것이고, 그렇지 않다면 아키텍처 부채가 있습니다.

🚀 최종 생각

  • 예측 가능한 메모리 사용
  • 깨끗한 해제
  • 쉬운 테스트
  • 보다 안전한 리팩터링
  • 빠른 온보딩
  • 생산 단계 버그 감소

다음 요소를 뒷받침합니다:

  • 모듈식 아키텍처
  • 멀티플랫폼 앱
  • 대규모 팀
  • 장기 유지 코드베이스

대규모에서 발생하는 대부분의 SwiftUI 문제는 SwiftUI 자체의 문제가 아니라 그래프 설계 문제입니다.

Back to Blog

관련 글

더 보기 »

SwiftUI #32: ProgressView

개요 ProgressView는 진행 표시줄을 생성합니다. 초기화 메서드 init_:value:total:은 첫 번째 인수로 레이블을 받습니다. value는 현재 진행 상황을 나타냅니다.

SwiftUI #25: 상태 (@State)

선언형 패러다임은 단순히 뷰를 어떻게 구성하는가에 관한 것이 아니라, 애플리케이션의 상태가 변할 때마다 뷰가 업데이트되어야 한다는 것이다.