디스패치(Dispatch)
- 프로그램이 메서드나 함수를 호출할 때 호출 대상(함수, 메서드)을 어떤 방식으로 결정하고 실행할지를 정의하는 메커니즘
- 성능 최적화, 코드 실행 방식, 다형성과 밀접하게 관련됨
Static Dispatch vs Dinamic Dispatch
1. 정적 디스패치 (Static Dispatch)
- 메서드나 함수의 호출이 컴파일 타임에 결정됨
- 주로 값 타입(struct, enum)이나 final로 선언된 클래스의 메서드에서 사용
- 컴파일러가 호출할 메서드를 미리 결정하므로 인라인(inline) 최적화가 가능하며, 호출 속도가 매우 빠름
→ 컴파일 시점에 호출 대상이 결정되고 런타임에서 바로 실행되기 때문에 성능이 우수함
2. 동적 디스패치 (Dynamic Dispatch)
- 메서드나 함수의 호출이 런타임에 결정됨
- 클래스의 상속 구조에서 메서드 오버라이딩과 같은 다형성을 지원하기 위해 사용
- 가상테이블(vtable)을 통해 호출할 메서드를 런타임에 확인하고 실행함
- vTable(Virtual Method Table) : 클래스마다 함수 포인터 배열을 생성해 메서드 참조를 관리하는 구조
→ 호출될 때마다 vTable을 조회하므로 Static Dispatch보다 성능이 떨어짐
Static Dispatch와 Dynamic Dispatch의 차이 정리
디스패치 방식 |
결정 시점 | 대상 | 속도 | 다형성 지원 |
정적 디스패치 (Static Dispatch) |
컴파일 타임 | 값 타입, final 메서드 | 매우 빠름 | 지원하지 않음 |
동적 디스패치 (Dynamic Dispatch) |
런타임 | 클래스 메서드(특히 오버라이딩) | 느림 | 지원 |
왜 Dynamic Dispatch가 필요한가?
- Dynamic Dispatch는 클래스 상속 구조에서 다형성을 지원하기 위해 필수적임
- 클래스는 “오버라이딩(상속)” 가능성이 있기 때문에 런타임에서 호출할 메서드를 결정
class Pet {
func printName(){
print("I'm Pet")
}
}
class Cat : Pet {
override func printName() { // 오버라이딩
print("I'm Cat")
}
}
let pet : Pet = Cat()
pet.printName() // "I'm Cat" (동적 디스패치)
- pet 변수의 타입은 Pet이지만, 실제 인스턴스는 Cat
- 위와 같이, 상위 클래스의 메서드가 하위 클래스에서 오버라이딩이 될 경우, 하위 클래스의 메서드를 사용해야 되기 때문에 어떤 메서드를 사용할지 결정하는 작업을 런타임에서 함 → 성능 저하
- 런타임 시점에 Cat의 printName() 메서드를 호출해야 하므로, vTable을 조회하여 적절한 메서드를 찾아 실행함
→ 클래스의 다형성을 지원하는 오버라이딩을 사용하기 위해 Dynamic Dispatch는 반드시 필요함
성능 최적화를 위한 Static Dispatch 사용법
- 클래스를 사용할 때 오버라이딩이 필요 없는 경우, Static Dispatch를 통해 성능 개선 가능
1. final 키워드
- final 키워드 : 해당 클래스, 메서드, 프로퍼티가 상속이 불가능하고 하위 클래스에서 오버라이딩 될 수 없음을 명시하는 키워드
final class Pet {
func printName(){
print("I'm Pet")
}
}
class Cat : Pet { // Error : Inheritance from a final class 'Pet
override func printName() { // Error : Instance method overrides a 'final' instance method
print("I'm Cat")
}
}
- final로 선언된 Pet 클래스는 상속이 불가능!
- 상속이 불가능하다면, 오버라이딩 가능성이 없기 때문에 컴파일러가 Static Dispatch로 동작하여 최적화 가능
2. Private 키워드
- Private : 접근 제한자로, 지정된 요소는 구현한 범위 내에서만 사용할 수 있음
- Private으로 선언된 메서드는 오버라이딩이 불가능함
- 컴파일러는 private 키워드로 지정된 요소가 참조될 수 있는 곳에서 오버라이딩이 되는지 판단!
- 만약, 오버라이딩 되는 곳이 없다면 final 키워드로 추론해서 Static Dispatch로 동작함
→ private으로 지정된 printName()메서드가 참조 될 수 없는 곳이므로 오버라이딩 불가능class Pet { private func printName(){ // 오버라이딩 가능성이 없으므로 Static Dispatch로 동작 print("I'm Pet") } } class Cat : Pet { override func printName() { // Error : Method does not override any method from its superclass print("I'm Cat") } }
오버라이딩 여부에 따른 Static vs Dynamic Dispatch
class Pet {
private var name = "Pet" // 참조 가능한 곳에서 오버라이딩 안 됨 -> Static Dispatch
private func printName(){ // 참조 가능한 곳에서 오버라이딩 됨 -> Dynamic Dispatch
print("I'm Pet")
}
class Cat : Pet {
override func printName() { // 참조 가능한 곳에서 오버라이딩
print("I'm Cat")
}
}
}
- private으로 지정된 printName() 메서드가 참조될 수 있는 곳에서 오버라이딩 됨 → Dynamic Dispatch로 동작
- private으로 지정된 name 프로퍼티는 참조될 수 있는 곳에서 오버리이딩이 되지 않으므로 final 키워드로 추론 → Static Dispatch로 동작
결론
- Static Dispatch는 컴파일 타임에 호출 대상이 결정되며, 성능 우수
- Dynamic Dispatch는 클래스의 다형성을 지원하며, 상속 구조에서 오버라이딩 메서드를 호출하기 위해 필수적
- 성능이 중요한 경우, final 또는 private 키워드를 활용하여 Static Dispatch로 최적화 가능
'Swift' 카테고리의 다른 글
Swift - 클로저의 캡처 파해치기 (값 타입, 참조 타입의 동작 차이) (0) | 2024.12.11 |
---|---|
Swift - GCD (Grand Central Dispatch) (3) | 2024.12.10 |
Swift - ARC (Automatic Reference Count) 정리 및 강한 순환 참조 해결 (1) | 2024.12.08 |
Swift - Struct와 Class 선택과 차이점 (0) | 2024.12.06 |
Swift - UICollectionViewFlowLayout 셀 크기 다르게 구현 (UILabel) (0) | 2024.12.02 |