J
[Swift] final 키워드와 Type에 따른 Dispatch 본문
`final` 키워드와 Type에 따른 Dispatch
`final` 키워드는 클래스, 메소드, 프로퍼티 등에서 사용되며, 해당 클래스, 메소드, 프로퍼티 등을 상속하거나 오버라이드할 수 없도록 함.
→ 변경 불가능한 클래스, 메소드, 프로퍼티 등을 만들기 위한 키워드.
성능 최적화 및 코드 안전성 향상에 큰 역할을 하며, static/dynamic dispatch의 차이와 관련.
1. `final` 키워드의 특징
- 클래스에 `final`을 사용하면 해당 클래스를 상속할 수 없음.
- 메소드나 프로퍼티에 `final`을 사용하면 해당 메소드를 오버라이드할 수 없음.
- `final`을 적용하면 컴파일 시점에 정적으로 호출이 결정되어 성능 최적화에 도움.
2. `final` 키워드 사용 예시
`final` 클래스
final class Animal {
var name: String
init(name: String) {
self.name = name
}
func makeSound() {
print("Animal sound")
}
}
// 아래 코드는 컴파일 에러 발생 (Animal을 상속할 수 없음)
// class Dog: Animal { }
`final` 메소드
class Animal {
final func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
// 아래 코드는 컴파일 에러 발생 (makeSound를 오버라이드할 수 없음)
// override func makeSound() {
// print("Bark!")
// }
}
3. Type에 따른 Dispatch
Dispatch는 메소드나 함수가 호출될 때 어떤 방식으로 해당 메소드를 찾고 실행할지를 결정하는 방법.
Swift에서는 크게 두 가지 종류의 Dispatch가 존재
- Static Dispatch (동적 디스패치): 컴파일 시점에 어떤 메소드가 호출될지 결정되는 방식.
- Dynamic Dispatch (정적 디스패치): 런타임 시점에 어떤 메소드가 호출될지 결정되는 방식.
4. Static Dispatch
Static Dispatch는 컴파일 시점에 메소드 호출이 결정되는 방식.
`final` 키워드가 적용된 클래스, 메소드, 프로퍼티, 구조체, 열거형 등.
런타임에 메소드 탐색 과정이 없으므로 성능이 더 빠르며 최적화가 가능.
예시
final class Animal {
func makeSound() {
print("Animal sound")
}
}
let animal = Animal()
animal.makeSound() // 컴파일 시점에 makeSound 메소드가 결정됨
특징
- 컴파일 시점에 메소드가 결정되므로 메소드 호출 속도가 빠름.
- 상속과 오버라이드를 허용하지 않기 때문에 메모리 비용이 적음.
5. Dynamic Dispatch
Dynamic Dispatch는 런타임 시점에 메소드 호출이 결정되는 방식.
클래스를 상속하거나 메소드를 오버라이드할 수 있는 경우, 메소드 호출은 vTable을 통해 진행.
Objective-C 런타임을 사용하여 메소드 탐색을 수행하므로, 런타임에 어떤 메소드가 호출될지를 결정.
따라서 약간의 성능 오버헤드가 발생할 수 있음.
예시
class Animal {
func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Bark!")
}
}
let animal: Animal = Dog()
animal.makeSound() // 런타임 시점에 Dog의 makeSound가 호출됨
특징
- 런타임 시점에 메소드가 결정되어 유연성이 높음.
- 상속과 오버라이드를 지원하므로 객체 지향 프로그래밍에서 자주 사용.
- 그러나 런타임에 메소드를 찾아야 하므로 성능이 Static Dispatch보다 느릴 수 있음.
6. vTable (Virtual Dispatch Table)
vTable은 클래스의 메소드를 관리하는 런타임 테이블로, 각 클래스마다 생성.
이 테이블은 클래스의 인스턴스 메소드 호출 시 어떤 메소드를 호출해야 할지를 런타임에 결정하는 데 사용.
작동 방식
- 클래스가 생성될 때, 해당 클래스에 포함된 가상 메소드(virtual method)의 주소가 vTable에 기록.
- 상속을 통해 메소드가 오버라이드되면, 부모 클래스의 메소드 대신 자식 클래스의 메소드 주소가 vTable에 저장.
- Dynamic Dispatch를 사용하는 경우, 메소드가 호출될 때 런타임에 vTable을 참조하여 적절한 메소드가 호출.
예시
class Animal {
func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Bark!")
}
}
let animal: Animal = Dog()
// animal.makeSound() 호출 시, 런타임에 vTable에서 Dog의 makeSound 메소드 주소를 찾아 호출
특징
- Dynamic Dispatch가 필요한 경우, 상속 구조에서 vTable을 통해 적절한 메소드를 동적으로 호출 가능.
- 런타임에 vTable을 참조하는 오버헤드가 발생하지만, 이는 객체 지향 프로그래밍의 유연성을 위한 비용.
- vTable은 클래스마다 존재하며, 상속된 클래스는 부모 클래스의 vTable을 확장하거나 오버라이드.
7. Static Dispatch vs. Dynamic Dispatch
Static Dispatch | Dynamic Dispatch | |
메소드 호출 시점 | 컴파일 시점에 메소드 호출이 결정 | 런타임 시점에 메소드 호출이 결정 (vTable 사용) |
사용 대상 | `final` 클래스, `final` 메소드, `static` 메소드, 구조체, 열거형 | 클래스의 오버라이드 가능한 메소드 |
성능 | 빠름 (메소드 탐색 없음) | 상대적으로 느림 (런타임에 메소드 탐색) |
유연성 | 상속과 오버라이드 불가능, 제한적 유연성 | 상속과 오버라이드 가능, 유연성이 높음 |
주요 사용 사례 | 성능 최적화가 중요한 경우, 상속 불필요한 경우 | 객체 지향 프로그래밍에서 상속과 오버라이드가 필요한 경우 |
8. final을 사용하는 이유와 성능 최적화 및 코드 안정성
성능 최적화
- `final` 을 사용하면 메소드가 정적으로 디스패치되기 때문에 런타임 시 메소드 탐색 과정이 제거되고 메소드 호출 속도가 빨라짐
- 상속과 오버라이드가 필요 없는 경우, `final`을 사용하여 런타임 오버헤드를 줄일 수 있음.
코드 안정성
- 상속이 필요하지 않거나, 상속이 되면 문제가 발생할 수도 있는 클래스나 메소드 등에 `final`을 사용하면 오버라이드를 방지하고 코드의 일관성을 유지할 수 있음.
- 특정 클래스나 메소드의 동작이 변경되지 않도록 보호할 수 있음.
'Swift' 카테고리의 다른 글
[Swift] Escaping Closure (@escaping) (0) | 2024.04.05 |
---|---|
[Swfit] Value Type, Reference Type (값 타입과 참조 타입) (0) | 2024.04.04 |
[Swift] Optimization Tips (0) | 2024.04.04 |
[Swift] WMO(Whole Module Optimization) (0) | 2024.04.03 |
[Swift] Access Control (접근 제어) (0) | 2024.04.03 |