다크 모드
아키텍처
Clean Architecture + MVVM
truloop iOS는 Clean Architecture 원칙에 기반한 레이어 분리와 MVVM 패턴의 프레젠테이션 구조를 결합하여 사용합니다.
레이어 다이어그램
레이어별 역할
1. App (Presentation Entry)
앱의 진입점으로, CompositionRoot를 통해 전체 의존성을 구성하고 화면 라우팅을 관리합니다.
| 구성 요소 | 역할 |
|---|---|
TRULOOP.swift | SwiftUI App 진입점 |
CompositionRoot | Swinject Container 구성, 모든 Assembly 등록 |
MainTabBar/ | 메인 탭 네비게이션 (MainTabBarController, LegacyMainTabBarController) |
AppScheme/ | URL Scheme / Deep Link 처리 (AppSchemeManager, AppSchemeHandler) |
Branch/ | Branch SDK 딥링크 핸들링 (BranchHandler) |
Managers/ | 이미지 업로드, Live Activity, 네트워크 에러 라우팅 등 |
LiveActivity/ | Live Activity Attributes 정의 |
Assembly/ | AppAssembly - App 레이어 DI 등록 |
2. Features (Presentation)
Feature 모듈의 내부 구조는 모듈마다 다릅니다.
Home Feature (대규모 모듈):
| 디렉토리 | 역할 |
|---|---|
Assembly/ | Swinject Assembly - 해당 Feature의 의존성 등록 |
PresentationSources/ | ViewModel 및 Presentation 모델 |
UISources/ | SwiftUI View 및 Builder |
Protocols/ | Feature 내부 프로토콜 정의 |
OnBoard Feature (화면별 분리 구조):
| 디렉토리 | 역할 |
|---|---|
Assembly/ | Swinject Assembly |
Views/{화면명}/ | 화면별로 Builder, View, ViewModel을 함께 배치 |
정보
Feature 모듈은 Domain, Repository에 의존하며, 다른 Feature 모듈에 직접 의존하지 않습니다. Feature 간 통신이 필요한 경우 OnBoardInterface와 같은 Interface 모듈을 통해 프로토콜 기반으로 통신합니다.
3. Domain
비즈니스 로직의 핵심 레이어입니다. 외부 프레임워크에 의존하지 않습니다.
| 구성 요소 | 역할 |
|---|---|
Entities/ | 비즈니스 모델 (User, Loop, Channel, Mission 등) |
Repository/ | Repository 프로토콜 정의 (AuthRepository, HomeRepository 등) |
UseCase/ | 비즈니스 로직 단위 (AccountUseCase, HomeUseCase 등) |
Assembly/ | Domain 레이어 DI 등록 |
4. Repository (Data)
Domain에서 정의한 Repository 프로토콜의 구현체를 제공합니다.
| 구성 요소 | 역할 |
|---|---|
DataSource/ | Repository 프로토콜 구현체 (예: AuthDataSourceImpl) |
Requests/ | 서버 요청 DTO |
Responses/ | 서버 응답 DTO |
Assembly/ | Repository 레이어 DI 등록 |
5. Networking (Infrastructure)
Moya 기반 네트워크 인프라를 제공합니다.
| 구성 요소 | 역할 |
|---|---|
API/ | Moya TargetType 구현 (AuthAPI, HomeAPI, DeviceAPI, FriendAPI, LiveActivityAPI 등) |
Networking/ | Networking 프로토콜, NetworkingImpl, NetworkingError, TruloopError |
Plugins/ | AuthInterceptor, ValidatingSession |
Assembly/ | Networking 레이어 DI 등록 |
데이터 흐름
ViewModel은 비즈니스 로직이 필요한 경우 UseCase를 통해, 단순 데이터 조회의 경우 Repository를 직접 호출합니다.
정보
실제 코드에서 HomeViewModel은 accountUseCase를 통해 계정 정보를 조회하면서도, homeRepository.fetchLoops()를 직접 호출하여 룹 목록을 가져옵니다. UseCase를 반드시 거치는 것은 아닙니다.
요청 흐름 예시
- View: 사용자가 룹 목록 화면에서 Pull-to-refresh 실행
- ViewModel:
homeRepository.loops(...)호출 (UseCase 없이 Repository 직접 호출) - Repository (
HomeDataSourceImpl):HomeAPI.loops(...)타겟으로 Networking 호출 - Networking (
NetworkingImpl):MoyaProvider<MultiTarget>을 통해 실제 HTTP 요청 수행 - 응답 처리:
LoopsResponseDTO를LoopsEntity로 변환하여 반환 - ViewModel:
@Published프로퍼티 업데이트로 View에 반영
Builder 패턴
Feature 모듈의 각 화면은 Builder 패턴을 사용하여 생성됩니다:
swift
// Protocol 정의
protocol HomeBuildable {
func build() -> HomeView
}
// Builder 구현
struct HomeBuilder: HomeBuildable {
let dependency: HomeDependency
func build() -> HomeView {
let viewModel = HomeViewModel(
homeRepository: dependency.homeRepository,
// ...
)
return HomeView(viewModel: viewModel)
}
}이 패턴을 통해:
- 화면 생성 시 필요한 의존성을 명확히 선언
- Swinject Assembly에서 Builder를 등록하여 DI 체인 구성
- 테스트 시 Mock Builder를 주입 가능
변경 이력
| 날짜 | 변경 내용 |
|---|---|
| 2026-03-10 | 소스 코드 기반 검증: 데이터 흐름 다이어그램에 ViewModel→Repository 직접 호출 경로 반영, Feature 모듈별 내부 구조 차이 명시, App/Networking 레이어 구성 요소 보완 |