1. Method Dispatch
Method Dispatch는 우리가 일반적으로 알고 있는
GCD(Grand Central Dispatch)의 Dispatch와는 다른 개념입니다.
Method Dispatch는 메소드를 호출할 때
어떤 메소드 구현을 사용할지 결정하는 과정을 의미합니다.
오늘은 Method Dispatch 중 Static Dispatch(정적 디스패치)와
Dynamic Dispatch(동적 디스패치)개념에 대해서 알아보겠습니다.
2. 정적 디스패치 (Static dispatch)
struct Cat {
func dream() {
print("Dream!")
}
}
var cats = [Cat]()
//cats 배열에 요소를 추가하는 코드가 있다고 가정
for cat in cats {
cat.dream()
}
cats 배열에 있는 모든 Cat 인스턴스에서 dream 메소드를 호출할 때, 어떤 메소드가 실행될지는 너무 명확합니다.
그리고 이런 내용은 컴파일 타임에 알 수 있습니다.
이렇게 컴파일 타임에 어떤 메소드의 동작을 실행할지 결정할 수 있는 경우 정적 디스패치라고 합니다.
기본적으로 값타입은 실행할 메소드가 명확하기 때문에 정적 디스패치가 적용됩니다.
이렇게 컴파일 타임에 어떤 메소드의 동작이 실행되는지 알 수 있으면, inlining 같은 최적화 기법을 사용할 수 있습니다.
2-1. inlining
inlining이란?
메서드를 호출하는 곳에 해당 메소드의 실제 구현을 대체하여 함수 호출 오버헤드를 줄이는 컴파일러 최적화 기법입니다.
예시를 통해 알아보겠습니다.
아래 코드는 Cat 인스턴스를 만든 뒤, sleep 메소드를 호출하고 있습니다.
struct Cat {
func sleep() {
dream()
}
func dream() {
print("Dream!")
}
}
let cat = Cat()
cat.sleep()
sleep 메소드는 내부에서 dream 메소드를 실행합니다.
sleep 메소드와 dream 메소드는 정적 디스패치로, inlining을 사용할 수 있습니다.
따라서 컴파일러는 sleep 메소드 호출을 실제 구현 구문으로 대체하고, dream 메소드 역시 실제 구현으로 대체합니다.

결과적으로 인스턴스를 생성하고, 바로 구현부를 실행할 수 있습니다.
이렇게 inlining 기법을 사용하면, 함수 호출의 오버헤드를 줄일 수 있어 프로그램이 더 빨라질 수 있습니다.
3. 동적 디스패치 (Dynamic Dispatch)
class Drawable {
func draw(){
print("Draw!")
}
}
class Point: Drawable {
var x = 0.0
var y = 0.0
override func draw() {
print("Point Draw")
}
}
class Line: Drawable {
var x = 0.0
var y = 0.0
override func draw() {
print("Line Draw")
}
}
var drawables = [Drawable]()
//drawables 배열에 요소를 추가하는 코드가 있다고 가정
for drawable in drawables {
drawable.draw()
}
drawables 배열에는 Point와 Line 클래스의 인스턴스가 모두 포함될 수 있습니다.
즉, draw메소드를 호출할 때
- Point의 draw 메소드가 될 수도 있고,
- Line의 draw 메소드가 될 수도 있습니다.
결과적으로 컴파일 타임에 어떤 메서드 동작이 실행되는지 알 수 없어 런타임에 해당 구현(메소드의 동작)이 결정됩니다.
이렇게 런타임에 결정해야 한다면, 이를 동적 디스패치라고 합니다.
4. class
예시에서 본 것처럼 class는 동적 디스패치가 적용됩니다.
동적 디스패치가 적용된 class를 사용하면, 어떻게 올바른 메서드를 호출할 수 있는지 알아보겠습니다.
4-1. virtual method table (V-Table)
각 클래스는 정적 메모리에 저장된 타입 정보를 가리키는 포인터를 가지고 있습니다.
그리고 타입 정보는 실행해야 할 메소드에 대한 포인터가 들어 있는 가상 메소드 테이블(virtual method table)과 연결되어 있습니다.

그래서 draw를 호출할 때, 컴파일러는 타입 정보를 통해 실행해야 할 메소드에 대한 포인터가 들어 있는 가상 메소드 테이블(virtual method table)을 조회하는 코드를 생성합니다.
한마디로 drawable.draw() 구문은 가상 메소드 테이블을 통해 올바른 draw 구현을 찾아 호출하라는 뜻이 됩니다.

4-2. 동적 디스패치가 필요하지 않다면
각 디스패치 방식 간의 큰 성능 차이는 없습니다.
하지만 동적 디스패치는 inlining 같은 최적화가 어렵기 때문에 결과적으로는 성능에 영향을 미칠 수도 있습니다.
따라서 override가 필요하지 않은 경우라면 final 키워드를 작성해 컴파일러가 메서드를 정적 디스패치로 처리하게 할 수 있습니다.
class Drawable {
final func draw(){
print("Draw!")
}
}
다른 방법으로는 클래스 자체의 상속을 막아 정적 디스패치로 처리하게 할 수도 있습니다.
final class Drawable {
func draw(){
print("Draw!")
}
}
타입 프로퍼티의 경우 class 키워드가 아닌 static 키워드를 사용해서 override 하지 않는 것을 명시적으로 알려줄 수 있습니다.
class Drawable {
static func draw(){
print("Draw!")
}
}
4. protocol
지금까지 struct와 class의 메소드가 어떤 Method Dispatch로 처리되는지 알아봤습니다.
그렇다면 protocol을 준수한 구조체는 어떨까요?
class에서 사용했던 예시를 프로토콜과 구조체로 변경했습니다.
protocol Drawable {
func draw()
}
struct Point: Drawable {
var x = 0.0
var y = 0.0
func draw() {
print("Point Draw")
}
}
struct Line: Drawable {
var x = 0.0
var y = 0.0
func draw() {
print("Line Draw")
}
}
var drawables = [Drawable]()
//drawables 배열에 요소를 추가하는 코드가 있다고 가정
for drawable in drawables {
drawable.draw()
}
컴파일 타임에는 Drawable이라는 것만 알고, 실제 구현을 알 수 없습니다.
즉, 동적 디스패치가 적용됩니다.
4-1. Protocol Witness Table (PWT)
class와 차이가 있다면 프로토콜은 Protocol Witness Table(PWT)을 사용합니다.
Protocol Witness Table(PWT)은 프로토콜을 준수한 타입마다 존재하며, 실제 구현이 연결되어 있습니다.

"타입 메소드에서 상속이 가능한 키워드와 불가능한 키워드를 왜 나눴을까?"라는
궁금증을 시작으로 Method Dispatch를 알아봤습니다.
Understanding Swift Performance영상에서 나온 만큼 Method Dispatch는 성능 최적화에서 중요한 부분을 차지하고 있습니다.
참고로 Xcode 8부터는 전체 모듈 최적화가 활성화되어 있기 때문에 컴파일러가 더 많은 최적화를 수행하고 있습니다.
더 많은 내용은 아래 영상에서 확인하실 수 있습니다!
출처: Understanding Swift Performance - WWDC16
Understanding Swift Performance - WWDC16 - 비디오 - Apple Developer
In this advanced session, find out how structs, classes, protocols, and generics are implemented in Swift. Learn about their relative...
developer.apple.com
'iOS > Swift' 카테고리의 다른 글
Swift) 메소드 - 인스턴스 메소드/타입메소드 (0) | 2024.10.11 |
---|---|
Swift) 프로퍼티(5/5) - 프로퍼티 래퍼 (0) | 2024.09.10 |
Swift) 프로퍼티(4/5) - 프로퍼티 옵저버/willSet/didSet (1) | 2024.09.02 |
Swift) 프로퍼티(3/5) - 인스턴스 프로퍼티/타입 프로퍼티/static/class (0) | 2024.08.21 |
Swift) 프로퍼티(2/5) - 연산 프로퍼티/get/set/Read-Only (0) | 2024.08.14 |