SwiftUI에서 모듈형 기능 아키텍처

발행: (2025년 12월 11일 오전 07:44 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

🧩 1. 기능 모듈이란?

A feature module is a self‑contained unit representing one functional chunk of your app:

Home/
Profile/
Settings/
Feed/
Auth/
OfflineSync/
Notifications/

Each module contains:

  • 뷰모델
  • 모델
  • 서비스
  • 라우팅 정의
  • 프리뷰
  • 목업

A feature should be removable without breaking the app. If you can delete a folder and nothing else breaks → it is modular.

📁 2. 권장 폴더 구조

Here is a clean, scalable pattern:

AppName/

├── App/
   ├── AppState.swift
   ├── AppEntry.swift
   └── RootView.swift

├── Modules/
   ├── Home/
   ├── HomeView.swift
   ├── HomeViewModel.swift
   ├── HomeService.swift
   ├── HomeRoute.swift
   └── HomeMocks.swift

   ├── Profile/
   ├── ProfileView.swift
   ├── ProfileViewModel.swift
   ├── ProfileService.swift
   ├── ProfileRoute.swift
   └── ProfileMocks.swift

   ├── Settings/
   ├── SettingsView.swift
   ├── SettingsViewModel.swift
   └── SettingsMocks.swift

   └── Shared/
       ├── Components/
       ├── Models/
       ├── Utilities/
       └── Styles/

├── Services/

└── Resources/

No more “God folders”—each module is its own world.

🔌 3. 모듈 경계에서의 의존성 주입

Each module declares what it needs via protocols:

protocol ProfileServiceProtocol {
    func fetchProfile(id: String) async throws -> Profile
}

The module does not know about concrete implementations (real API client, offline cache, mock data source, testing environment). The app injects the concrete service:

ProfileViewModel(
    service: appServices.profileService
)

장점

  • 테스트 용이성
  • 유연성
  • 쉬운 교체
  • 격리

🧭 4. 모듈 간 라우팅

Each module defines its own route type:

enum ProfileRoute: Hashable {
    case details(id: String)
    case followers(id: String)
}

The root view aggregates module routes:

enum AppRoute: Hashable {
    case profile(ProfileRoute)
    case home
    case settings
}

Central navigation handling:

.navigationDestination(for: AppRoute.self) { route in
    switch route {
    case .profile(let pr): ProfileRouter.view(for: pr)
    case .home: HomeView()
    case .settings: SettingsView()
    }
}

Module‑specific router:

struct ProfileRouter {
    @ViewBuilder static func view(for route: ProfileRoute) -> some View {
        switch route {
        case .details(let id):
            ProfileView(userID: id)
        case .followers(let id):
            FollowersView(userID: id)
        }
    }
}

Modules stay independent.

🧪 5. 기능 모듈은 완전한 프리뷰가 가능해야 함

Example preview:

#Preview("Profile Details") {
    ProfileView(
        viewModel: ProfileViewModel(
            service: MockProfileService()
        )
    )
}

자체 포함 프리뷰

  • 개발 속도 향상
  • 회귀 방지
  • 인지 부하 감소

The previews folder becomes a mini design system per module.

🏎 6. 모듈화로 얻는 성능 향상

A modular codebase brings:

  • 빌드 시간 단축 – 변경된 모듈만 다시 컴파일됩니다.
  • 안전한 리팩토링 – 모듈이 내부 세부 정보를 누출하지 않습니다.
  • 테스트 격리 향상 – 모듈별로 테스트를 실행합니다.
  • 인지 부하 감소 – 새로운 기여자가 기능을 빠르게 이해합니다.
  • CI 병렬화 개선 – 각 모듈을 별도 테스트 타깃으로 사용할 수 있습니다.

These gains are especially noticeable in apps with 10+ screens.

🔄 7. 기능 통신 패턴

Modules should not import each other. Use one of these patterns instead:

  • AppRoute (most common) – root coordinates navigation. → AppRoute (가장 일반적) – 루트가 네비게이션을 조정합니다.
  • Services layer – modules communicate through shared service protocols. → 서비스 레이어 – 모듈이 공유 서비스 프로토콜을 통해 통신합니다.
  • Event bus – for global side effects like analytics. → 이벤트 버스 – 분석과 같은 전역 부수 효과에 사용합니다.
  • Shared models – placed explicitly under Modules/Shared. → 공유 모델Modules/Shared에 명시적으로 배치합니다.

Key rule: 📌 Modules talk “upward”, not sideways.

🧵 8. 각 모듈 내부에 비즈니스 로직 격리

All module logic belongs in the module:

Profile/
   ProfileViewModel.swift
   ProfileService.swift
   ProfileValidation.swift
   ProfileFormatter.swift

Avoid:

  • 전역 유틸리티
  • 공유 상태
  • 관련 없는 모듈 임포트

This keeps code ownership clean.

🧱 9. 앱 전반에 걸친 공유 모듈

Your Modules/Shared folder should contain:

  • 기본 컴포넌트
  • 타이포그래피
  • 테마 시스템
  • 전역 모델
  • 네트워킹 유틸리티
  • 애니메이션 헬퍼

Never place feature‑specific logic here.

🧭 10. 언제 모듈화를 해야 할까?

Use this checklist:

  • 앱에 6개 이상의 화면이 있다
  • 두 명 이상의 개발자가 코드베이스에서 작업한다
  • 기능이 독립적으로 배포된다
  • 빠른 프리뷰가 필요하다
  • 안전한 리팩토링이 필요하다
  • 딥 링크를 사용한다
  • 오프라인 모드를 지원한다
  • DI + 전역 AppState를 사용한다

If you checked 3 or more → modularize.

🚀 최종 생각

Modularizing a SwiftUI app transforms your codebase:

  • 작업이 더 쉬워진다
  • 테스트가 더 쉬워진다
  • 확장이 더 쉬워진다
  • 리팩토링이 더 쉬워진다
  • 협업이 더 쉬워진다

It’s one of the biggest upgrades you can make to your architecture.

Back to Blog

관련 글

더 보기 »

Swift #6: 옵셔널

옵셔널 때때로 변수에 값이 없음을 표시해야 할 필요가 있습니다. 이러한 경우에 Swift는 `?` 수정자를 제공하여 모든 타입을 옵셔널로 변환합니다.