Method Dispatch란?
"내가 호출할 함수가 무엇인지 결정하는 과정"
가장 쉬운 예를 들어보자.
부모 클래스와 자식 클래스가 있을 경우,
class Animal {
init() {
print("Animal Created")
}
func makeNoise() {
fatalError("Must Override to get specific Noise")
}
}
class Cat: Animal {
override init() {
print("Cat created")
}
override func makeNoise() {
print("Mews")
}
}
let cat = Cat()
cat.makeNoise()
cat.makeNoise()는 Animal의 메소드를 호출할 것인가, Cat의 메소드를 호출할 것인가?
위의 과정을을 결정해주는 것이 Method Dispatch가 하는 역할이다.
1) Static Dispatch
정적 디스패치는 밑에 비교할 동적 디스패치에 비해 시간이 빠르다.
이유는 컴파일 시에 어떤 함수를 호출할 지 미리 다 결정해두기 때문이다.
2) Dynamic Dispatch
동적 디스패치는 위와 다르게 런타임에 호출될 함수가 결정된다.
런타임에 코드를 따라 실제 할당된 객체의 타입을 보고 맞는 요소를 참조한다.
Table Dispatch
사실 일반적인 동적 디스패치라고 알려져있지만, Message Dispatch와 구분하기 위해 Table Dispatch라고 하기도 한다.
vTable(Virtual Dispatch Table)을 참조해 결정을 하게 된다.
여기서 vTable은 클래스마다 어떤 걸 호출해야하는 지를 담아둔 함수 포인터 배열이다!
이 코드의 흐름을 따라가는 과정에서 오버헤드가 일어날 수 있다.
오버헤드란?
오버헤드란 프로그램의 실행흐름에서 나타나는 현상중 하나입니다. 예를 들어, 프로그램의 실행흐름 도중에 동떨어진 위치의 코드를 실행시켜야 할 때, 추가적으로 시간, 메모리, 자원이 사용되는 현상입니다.
출처: https://gamestory2.tistory.com/15
그림을 보면 한눈에 이해할 수 있어서 가져왔다.
이래서 정적 디스패치에 비해 시간이 오래 걸릴 수 밖에 없는 구조.
vTable에서 메모리 주소를 읽고, 해당하는 위치로 가야하기 때문에 느려진다.
하지만 Message Dispatch보다는 속도가 빠르다고 한다.
상속 가능성을 열어두는 것은 다형성 활용에 유리하겠지만,
상속 가능성을 배제해버리면 이런 오버헤드를 막아버릴 수 있기 때문에 코드 효율을 올릴 수 있다.
Message Dispatch
메세지 디스패치는 사실 동적 디스패치의 종류 중 하나이다!
하지만 보통 구분을 위해 따로 말을 사용한다.
Table Dispatch는 자신이 처리하는 모든 함수에 대해 포인터를 가지고 vTable에 담아둔다.
그에 비해, Message Dispatch는 자기 자신이 새로 재정의한 함수들에 대해서만 Table에 담아두고, 부모 타입이 가지고 있는 메소드들에 대해서는 부모의 Table에 가서 찾아서 실행한다.
사실 이 Message Dispatch는 Objective-C 런타임을 사용해야하한다..!
Swift 컴파일러에서 이 기능을 즉시 제공하지 않는다. 하지만 Objective-C 런타임과 연결시켜 사용할 수 있다.
장점으로는,
이 동적 디스패치 기술은 가장 동적(말장난 의도)입니다. 실제로 Cocoa 프레임워크가 KVO, Core Data 및 기타 항목과 같은 많은 대기업 내부에서 이를 사용하기 때문에(최적화 부분을 제외하고) 매우 좋습니다.
또한 메서드 swizzling 을 활성화합니다. 이는 일반적으로 이 기술을 사용하여 런타임에 메서드의 기능을 변경할 수 있음을 의미합니다.
이러한 점들이 있지만, 이 디스패치 기술을 활용하기 위해
@objc를 명시적으로 표시해야하는 등
굳이 이렇게 사용해야한다는게 Swift에 들어선 지금은 썩 달갑지 않은 것 같다.
Dispatch in extension
Swift에서는 extension에서 작성된 메소드들에 대해 재정의하는 것을 막아놨다.
이러한 동작의 이유는 확장으로 작성된 메서드가 클래스의 vTable에 추가되지 않기 때문이라고 한다.
따라서 확장에서의 Dispatch는 Static Dispatch를 따르게 되어있다.
Dispatch in protocol
일단 프로토콜은 채택을 한다면, 내가 어떤 기능을 작성할 지 약속을 하는 것이다.
상속과 달리 프로토콜은 열거형, 구조체, 클래스에서 얼마든지 채택하여 사용할 수 있다.
protocol Container {
func getValue()
}
// Value: 3 words in size
struct ContainerOne : Container {
var stringValue: String
func getValue() {}
}
// Value: 1 word in size
struct ContainerTwo : Container {
var intValue: Int
func getValue() {}
}
// How do we represent this in memory?
var testContainer: Container
이러한 경우에,
ContainerOne에서는 String Type을 가지고 있고
ContainerTwo에서는 Int Type을 가지고 있다.
따라서 두 구조체 간에도 메모리 할당 공간의 차이가 생기는 데 testContainer는 어떻게 메모리를 표시할 것이냐?
Swift에서는 이 경우에 대비해 컨테이너 유형을 만들어해결한다고 한다!
1. Value Buffer: 실제 인스턴스를 저장
2. VWT(Value Witness table): 값 생성, 복사 및 삭제에만 사용되는 V-테이블
3. PWT(Protocol Witness table): V-table과 유사하게 작동, 주어진 유형에 대한 프로토콜 요구 사항의 기능 구현에 대한 일련의 포인터
4. 하나 이상의 프로토콜을 참조하면 PWT를 추가한다!
이러한 테이블을 참조하며 Dispatch를 한다고 한다.
참고
https://medium.com/@bakshioye/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d
https://heartbeat.comet.ml/understanding-method-dispatch-in-swift-684801e718bc
'Swift' 카테고리의 다른 글
[Swift] Swift의 코드 성능과 Dispatch (0) | 2022.09.25 |
---|