← Home
Clean Architecture · Dispatch · Q&A

Clean Architecture + Dispatch Q&A

2026-06-26 학습 중 나온 질문과 답변 정리

1. Clean Architecture 심화

UseCase 구현체는 어디에 있어?

Domain Layer에 있다. Protocol과 구현체 둘 다.

Domain/
├── UseCases/
│   └── SearchMoviesUseCase.swift  ← Protocol + 구현체 둘 다
└── Interfaces/Repositories/
    └── MoviesRepository.swift     ← Protocol만 (구현체는 Data Layer)

이유: UseCase 구현체는 순수 비즈니스 로직만 담음. 네트워크/DB 코드 없음. Repository는 외부 의존성 있어서 구현체가 Data Layer에.

toDomain()은 어디서 호출해?

Data Layer의 Repository 구현체에서 호출한다.

// Data/Repositories/DefaultMoviesRepository.swift
func fetchMoviesList(...) {
    dataTransferService.request(with: endpoint) { result in
        case .success(let responseDTO):
            completion(.success(responseDTO.toDomain()))  // ⭐ 여기
    }
}

UseCase는 DTO 존재 자체를 모른다. Entity만 받음.

DI와 DIP 차이?
DI (Dependency Injection)DIP (Dependency Inversion)
한국어의존성 주입의존성 역전 원칙
분류기법 (How)원칙 (Why)
의미외부에서 의존성을 넣어준다추상화(Protocol)에 의존해라

DIP: 고수준(UseCase)이 저수준(Repository 구현체)을 직접 의존하지 않고, 둘 다 Protocol에 의존.

Observable의 observer 파라미터는 왜 필요해?

구독 해제 시 식별자 역할. 클로저는 비교 불가능해서 누가 등록했는지 알 수 없음.

// observer 없으면
func remove(???) {
    // 어떤 클로저를 지워야 하지? 클로저끼리 == 안 됨
}

// observer 있으면
func remove(observer: AnyObject) {
    observers = observers.filter { $0.observer !== observer }
}

간단한 앱이면 observer 없이 클로저만 써도 됨. 구독 해제가 필요하면 필요.

Coordinator가 VC를 소유해?

아니다. NavigationController가 소유한다.

Coordinator
    └── weak navigationController  // 참조만

NavigationController
    └── viewControllers: [VC1, VC2, ...]  // ⭐ 여기서 소유

Coordinator는 VC를 생성하고 push만 하고, 저장하지 않음.

A → B → C → D면 A가 D를 소유한다고 못 해?

할 수 있다. 전부 strong이면 A가 D를 간접 소유.

표현의미
A가 B를 직접 소유A가 B를 strong 참조
A가 D를 간접 소유A 해제되면 → B → C → D 다 해제됨
ViewModel은 Actions를 어떻게 소유해?
class DefaultMoviesListViewModel {
    private let actions: MoviesListViewModelActions?  // ⭐ 프로퍼티로 저장

    init(actions: MoviesListViewModelActions?) {
        self.actions = actions
    }
}

Actions는 struct. 안에 클로저들이 Coordinator 메서드를 캡처.

메서드 전달 시 weak self 안 해도 돼?

상황에 따라 다름. 메서드 참조는 self를 strong 캡처함.

// strong 캡처
let actions = Actions(showDetail: showDetail)

// weak 캡처 (안전)
let actions = Actions(showDetail: { [weak self] movie in
    self?.showDetail(movie: movie)
})

Coordinator가 앱 수명 동안 살면 생략 가능. 아니면 weak self 권장.

2. Static vs Dynamic Dispatch

호출할 때 결정되는 게 Dynamic Dispatch?

맞다. 런타임에 어떤 메서드 실행할지 결정.

Static DispatchDynamic Dispatch
결정 시점컴파일 타임런타임
방법함수 주소 고정vtable/PWT 조회
제네릭 + PWT도 Dynamic Dispatch야?

아니다. 제네릭은 호출부에서 컴파일 타임에 T 확정.

any Drawable (existential)<T: Drawable> (generic)
타입 결정런타임컴파일 타임
DispatchDynamicStatic (간접 호출)
// Dynamic — 런타임에 타입 확인
let item: any Drawable = Circle()
item.draw()  // PWT 조회, 타입 모름

// Static — 컴파일 타임에 T = Circle 확정
func process<T: Drawable>(_ item: T) { item.draw() }
process(Circle())  // PWT 파라미터로 전달, 타입 알고 있음
프로토콜 conform해서 사용하면 Dynamic Dispatch 맞지?

맞다. 프로토콜 타입으로 쓰면 Dynamic Dispatch.

let item: Drawable = Circle()  // 프로토콜 타입
item.draw()  // ⭐ Dynamic Dispatch (PWT 조회)
사용 방식Dispatch
let item: Drawable = ...Dynamic
let item: any Drawable = ...Dynamic
func foo<T: Drawable>(_ item: T)Static
let item: Circle = ...Static
Dispatch가 함수만 해당? 프로퍼티도?

프로퍼티도 포함. 프로퍼티는 내부적으로 getter/setter 메서드.

protocol Drawable {
    var color: String { get }  // getter 메서드
    func draw()
}

let item: Drawable = Circle()
item.color  // ⭐ Dynamic Dispatch (PWT에서 getter 조회)
item.draw() // ⭐ Dynamic Dispatch
Static Dispatch = Inlining?

아니다. Static Dispatch는 Inlining의 전제 조건일 뿐.

Static DispatchInlining
의미컴파일 타임에 함수 확정함수 본문을 호출부에 복사
결과함수 주소로 직접 호출함수 호출 자체가 없어짐

Static Dispatch여도 컴파일러 판단에 따라 인라인 안 될 수 있음.

같은 모듈이면 인라인 되고, 다른 모듈이면 안 돼?

기본적으로 맞다.

상황인라인
같은 모듈✅ 가능 (컴파일러 판단)
다른 모듈 (기본)❌ 불가 (본문 못 봄)
다른 모듈 + @inlinable✅ 가능
정적 타입에 있는 메서드만 호출 가능해?

맞다. 런타임에 뭐가 들어있든 컴파일러는 정적 타입만 본다.

protocol Drawable { func draw() }

class Circle: Drawable {
    func draw() { }
    func resize() { }
}

let a: Circle = Circle()
a.draw()    // ✅ Circle에 있음
a.resize()  // ✅ Circle에 있음

let b: Drawable = Circle()
b.draw()    // ✅ Drawable에 있음
b.resize()  // ❌ Drawable에 없음 (컴파일 에러)
선언정적 타입호출 가능
let a: Circle = Circle()Circledraw(), resize()
let b: Drawable = Circle()Drawabledraw()만
let c: any Drawable = Circle()any Drawabledraw()만
Existential Container에 metadata가 있어도 왜 못 불러?
metadata가 vtable을 가리키고 있어도, 컴파일러가 타입 안전성을 보장 못하면 호출 자체를 막는다. Dynamic Dispatch는 "어떤 구현체"를 런타임에 정하는 거지, "메서드 존재 여부"는 컴파일 타임에 정적 타입으로 확인.
// 호출하고 싶으면 다운캐스팅
if let circle = b as? Circle {
    circle.resize()  // ✅ OK
}
vtable은 항상 Dynamic Dispatch?

vtable 조회하면 Dynamic. 하지만 vtable 있어도 최적화로 조회 생략 가능.

상황Dispatch
일반 class 메서드Dynamic (vtable)
final class / final funcStatic
private funcStatic
static funcStatic
class Animal {
    func speak() { }      // vtable에 등록됨
    final func eat() { }  // vtable에 없음 (final)
}

let a: Animal = Dog()
a.speak()  // vtable 조회 → Dynamic
a.eat()    // vtable 조회 안 함 → Static
Static Dispatch면 함수 주소는 어디에?

호출하는 코드에 직접 박혀있다. 인스턴스에 저장되는 게 아님.

Dynamic (vtable)Static
함수 주소 위치인스턴스 metadata → vtable호출 코드에 직접
런타임 조회✅ 필요❌ 불필요
Dynamic:  인스턴스 → metadata → vtable → 함수 주소 → 호출
Static:   call 0x1234ABCD  (주소가 코드에 박힘)

3. Existential Container / Type Metadata

Value Buffer + Type Metadata ptr + PWT ptr에서 2번이 뭐야?

Type Metadata Pointer = 실제 타입 정보를 가리키는 포인터

Existential Container (40 bytes)
┌─────────────────────────────┐
│ Value Buffer (24B)          │ ← 값 저장
├─────────────────────────────┤
│ Type Metadata Pointer (8B)  │ ← ⭐ 실제 타입 (is, as? 등)
├─────────────────────────────┤
│ PWT Pointer (8B)            │ ← 프로토콜 메서드 테이블
└─────────────────────────────┘

Type Metadata는 타입 확인(is, as?), 메모리 관리에 사용.

VWT는 어딨어?

Type Metadata 안에 있다. Existential Container에 직접 없음.

Existential Container
    │
    └── Type Metadata Pointer ──→ Type Metadata
                                      │
                                      └── VWT Pointer ──→ Value Witness Table
                                                           ├── copy()
                                                           ├── move()
                                                           ├── destroy()
                                                           ├── size
                                                           └── alignment

VWT = 값 복사/이동/해제 방법. 런타임에 타입 크기, 관리 방법 모를 때 사용.

Type Metadata에 vtable 있다던데, any Class 대비야?

아니다. vtable은 class 상속 다형성용. any랑 무관하게 class면 항상 있음.

// Class는 원래 vtable 사용
class Animal { func speak() { } }
class Dog: Animal { override func speak() { } }

let animal: Animal = Dog()
animal.speak()  // vtable로 Dog.speak() 찾음

// Protocol은 PWT 사용
let drawable: Drawable = Circle()
drawable.draw()  // PWT로 Circle.draw() 찾음
let animal: Animal = Dog()에서 vtable로 Dog.speak() 못 찾아야 하는 거 아냐?

찾는다. 인스턴스 자체가 Dog의 metadata를 가리키고 있어서.

let animal: Animal = Dog()

animal 변수
    │
    ▼
Dog 인스턴스 (힙)
┌─────────────────────┐
│ metadata ptr ───────│──→ Dog Type Metadata (Animal 아님!)
│ (stored properties) │          │
└─────────────────────┘          ▼
                            Dog vtable
                            └── speak() → Dog.speak()

핵심: 변수 타입(Animal)은 컴파일 타임용. 런타임엔 인스턴스의 실제 metadata를 따라감.

Dynamic Dispatch의 본질: 컴파일러는 "Animal.speak()" 호출인 것만 앎. 런타임에 인스턴스 metadata 보고 실제 타입(Dog)의 메서드 호출.