Swift에서 함수를 실행시키는 방법이 크게 세 가지가 있다. Static Dispatch라고도 부르는 Direct Dispatch, Dynamic Dispatch라고도 부르는 Table Dispatch, 그리고 마지막으로 Message Dispatch가 있다.
사실 우리는 그냥 함수를 정의하고 실행시키는 부분만 관여하지만 내부적으로 메서드 실행방법은 성능과도 관련이 있기 때문에 알아보도록 하자!!
Swift의 메서드 실행 방법 (1) | Direct(Static) Dispatch/ Table(Dynamic) Dispatch/ Message Dispatch
Swift의 메서드 실행 방법 (2) | table 기반 메커니즘 Virtual Table과 Witness Table
Swift의 메서드 실행 방법 (3) | 프로토콜 채택한 구조체 Existential Container / ValueBuffer / VWT / PWT
Direct Dispatch (Static Dispatch)
정적/직접 디스패치의 가장 큰 특징은 컴파일 타임에 실행한다는 점이다.
이 뜻은 컴파일 타임에 함수의 메모리 주소를 직접적으로 삽입하거나, 함수 명령 코드를 해당 위치에 직접적으로 심어버리는 것(in-line)을 뜻하며 컴파일 타임에 결정 나기 때문에 성능상 이점을 가질 수 있다는 점을 기억해 두자!
사용시점
Direct Dispatch는 value 타입(구조체, 열거형)에서 사용되는 방식이다. 또한, 단순한 값 타입의 형식에서는 상속이 불가하기 때문에 다형성의 장점을 누릴 수 없다.
하지만, 프로토콜타입을 준수하는 값(value)과 같은 경우에는 다형성이 가능해지기 때문에 이 경우를 살펴보자면, 프로토콜 정의 시에 본체의 요구사항에 대해서는 Witness Table으로 저장되어 Table Dispatch 방식으로 실행되지만 프로토콜 본체의 요구사항에는 없으면서 확장으로 기본 구현된 메서드는 Direct Dispach 방식으로 실행됨으로 프로토콜을 채택해서 다형성이 가능해지는 값타입의 경우 메서드가 다양하게 실행될 수 있는 부분도 고려를 해보면 좋겠다.
* 이번 글에서는 프로토콜을 준수하는 구조체의 경우에는 다형성을 구현할 수 있다는 점만 인지하고 넘어가고 다음 글 부터 자세히 설명해보겠다!
struct MyStruct {
func method1() { print("Struct - Direct method1") } //10-20
func method2() { print("Struct - Direct method2") } //30-40
}
let struct = MyStruct()
struct.method1() // 10 : 여기 코드에다가 실행되어야할 코드 영역의 메모리 주소(명령어 주소)를 바로 삽입해놓거나, 명령코드를 직접적으로 심어버린다
struct.method2() // 30
Table Dispatch (Dynamic Dispatch)
동적/테이블 디스패치는 런타임에 실행된다.
함수의 포인터(메모리 주소에 대한 정보)를 배열형태(table)로 보관했다가 런타임에 실제로 주소를 찾아가서 실행한다는 것이다. 런타임에 이러한 작업을 해주어야 하기 때문에 성능상으로 손해를 볼 수도 있어서 조심해야 한다.
사용시점
대표적으로 클래스, 프로토콜에서 정의된 메서드에 대한 실행방법이다.
다만, 어떤 타입으로 저장하냐에 따라 Virtual Table, Witness Table 중 어떤 테이블로 저장되는지가 달라진다! ( Virtual Table와 Witness Table 의 차이점에 대해서는 다음 글에서 살펴볼 예정이다 ~~)
class FirstClass { //Table Dispatch - Virtual Table : [110, 120]
func method1() { print("Class - Table method1") } //코드 영역 주소 110 - 119
func method2() { print("Class - Table method2") } //코드 영역 주소 120 - 129
}
// 자식클래스에서 테이블을 따로 보유
class SecondClass: FirstClass { //Table Dispatch - Virtual Table : [110, 130, 140]
override func method2() { print("Class - Table method2-2") } //코드 영역 주소 130-139
func method3() { print("Class - Table method3") } //코드 영역 주소 140-149
}
let first = FirstClass()
first.method1() //110 으로 가서 실행
first.method2() //120 으로 가서 실행
let second = SecondClass()
second.method1() //110 으로 가서 실행
second.method2() //130 으로 가서 실행 (재정의된 것)
second.method3() //140 으로 가서 실행 (상속 후 새로운 메서드)
protocol MyProtocol { //Table Dispatch - Witness Table
func method1()
func method2()
}
extension MyProtocol {
// 요구사항의 기본 구현 제공 ==> Witness Table
func method1() { print("Protocol - Witness Table method1") }
func method2() { print("Protocol - Witness Table method2") }
// 필수 요구사항은 아님 ==> Direct Dispatch
func anotherMothod() {
print("Protocol Extension - Direct method")
}
}
class FirstClass: MyProtocol {
func method1() { print("Class - Virtual Table method1") }
func method2() { print("Class - Virtual Table method2") }
func anotherMothod() { print("Class - Virtual Table method3") }
}
let first = FirstClass()
//클래스 타입으로 저장 => Virtual Table
first.method1() // Class - Virtual Table method1
first.method2() // Class - Virtual Table method2
first.anotherMothod() // Class - Virtual Table method3
let proto: MyProtocol = FirstClass()
//프로토콜 타입으로 저장 =>
// 본체 필수 요구 사항에 있는 건 Witness Table
// 본체 필수 요구 사항에 없으면서확장으로 기본 구현된건 Direct method
//⭐️ But, FirstClass에서 프로토콜 채택하고 메서드들을 재구현해주었기 때문에 Witness Table이 아닌 Virtual Table로 저장/실행된다
//⭐️ 만약, FirstClass에서 프로토콜 채택하고 메서드들을 재구현해주지 않았다면 Witness Table 형태로 저장/실행되었겠지
proto.method1() // Class - Virtual Table method1
proto.method2() // Class - Virtual Table method2
proto.anotherMothod() // Protocol Extension - Direct method 여기에바로 명령어 주소를 삽입하거나 명령어를 삽입
Message Dispatch
Message Dispatch는 위에서 본 두 가지 방법과 다르게 objective-C에서 사용하던 방식이기 때문에 비교적 간단하게만 알아보자면,
런타임(실제 앱이 실행되는 와중)에 어떤 메서드, 명령어를 실행해야 하는지 찾아서 실행한다. Virtual Table 방식과 유사하긴 하지만 다른 점은 상속 후 재정의하지 않는 메서드는 상위 클래스를 참조하고 있다는 점이다!
class ParentClass {
@objc dynamic func method1() { print("Class - Message method1") }
@objc dynamic func method2() { print("Class - Message method2") }
}
class ChildClass: ParentClass {
@objc dynamic override func method2() { print("Class - Message method2-2") }
@objc dynamic func method3() { print("Class - Message method3") }
}
'Swift' 카테고리의 다른 글
Swift의 메서드 실행 방법 (3) | 프로토콜 채택한 구조체 Existential Container / ValueBuffer / VWT / PWT (1) | 2024.01.29 |
---|---|
Swift의 메서드 실행 방법 (2) | table 기반 메커니즘 Virtual Table과 Witness Table (1) | 2024.01.23 |
[Swift] mutating 키워드와 COW(Copy On Write) (0) | 2023.12.24 |
[swift] 제어 전송문 정리 | fallthrough continue break return throw (1) | 2023.11.26 |
[Swift] 클로저와 중첩함수의 캡처 현상 (2) | 캡처리스트 클로저에서self키워드 (5) | 2023.11.09 |