final을 붙이면 성능이 향상된다.
왜???
이유를 알기 위해서는 Dispatch라는 것을 알아야 한다.
Dispatch
내가 호출할 함수를 컴파일 타임에 결정하냐, 런타임에 결정하냐에 따른 방식이다.
StaticDispatch (Direct Call)
컴파일 타임에 호출되니 함수를 결정하고 런타임에 그대로 실행된다.
컴파일 시간에 결정되기 때문에 성능상 이점이 있다.
Dynamic Dispatch (Indirect Call)
런타임에 호출될 함수를 결정한다.
swift는 클래스마다 함수 포인터들의 배열인 vTable이라는 것을 유지한다.
하위 클래스가 메서드를 호출할 때, vTable을 참조하여 실제 호출할 함수를 결정한다.
위와 같은 과정이 런타임에 발생하기 때문에 성능상 손해를 보게 된다
Reference Type에서의 Dispatch
Class는 상속의 가능성과 오버라이딩의 가능성이 있어서 Dynamic Dispatch를 사용한다고 한다.
실제 오버라이딩이 되던 안되던 vTable을 확인하여 참조한다. → 오버라이딩을 사용하기 위해서는 필수적으로 Dynamic Dispatch를 사용해야 한다.
Value Type에서의 Dispatch
구조체, 열거형은 상속을 할 수 없고 오버라이딩의 가능성이 없기 때문에 Static Dispatch를 사용한다.
Value Type extension에서의 Dispatch
value type이 상속을 할 수 없고 오버라이딩의 가능성이 없기 때문에 static Dispatch을 사용하듯 extension도 같은 이유이다.
Protocol에서의 Dispatch
메서드의 선언부만 제공하고 실제 사용할 때 프로토콜 타입을 참조로 사용하는 경우, 해당 인스턴스 타입에 맞는 메서드를 호출해야 하므로 Dynamic Dispatch를 사용한다.
Reference Type extension에서의 Dispatch
Class를 extension 하여 메서드를 추가하는 경우 서브 클래스에서 오버라이딩이 불가능해진다.
하지만 @objc를 사용하면 Objective-C 런타임을 사용하므로 오버라이딩이 가능해진다.
일반적인 경우는 Static Dispatch만 사용한다.
Protocol extension에서의 Dispatch
두 가지로 나뉘게 된다
- Protocol에 선언만 돼있는 메서드를 extension을 통해 default 메서드를 구현한 경우
- Protocol에 선언되어있지 않는 메서드를 추가로 구현한 경우
Protocol에 선언만 돼있는 메서드를 extension을 통해 default 메서드를 구현한 경우
소들이 님의 블로그 예시를 통해 이해해 보자
protocol Human {
func sayHello()
}
extension Human {
func sayHello() {
print("Hello Human!")
}
}
class Student: Human { }
class Teacher: Human {
func sayHello() {
print("Hello Teacher!")
}
}
Student 클래스의 경우 생성 후 sayHello를 호출하면 프로토콜 extension에 있는 디폴트 메서드가 호출된다.
Teacher 클래스의 경우 생성 후 sayHello를 호출하면 프로토콜 내부 메서드가 호출되어 사용된다.
따라서 호출되는 공간이 다르므로 Dynamic Dispatch로 동작한다.
Protocol에 선언되어있지 않는 메서드를 추가로 구현한 경우
소들이 님 예시를 통해 알아보자
protocol Human { }
extension Human {
func sayHello() {
print("Hello Human!")
}
}
class Student: Human { }
class Teacher: Human {
func sayHello() {
print("Hello Teacher!")
}
}
Student 클래스의 경우 생성 후 sayHello를 호출하면 프로토콜 extension에 있는 디폴트 메서드가 호출된다.
Teacher 클래스의 경우 생성 후 sayHello를 호출하면 프로토콜 extension에 있는 디폴트 메서드가 호출된다.
같은 공간에서 호출되므로 Static Dispatch로 동작한다.
Dynamic Dispatch 성능 상의 문제점
위 예제들을 통해 설명했다시피 상속! 오버라이딩! 때문에 Class가 Dynamic Dispatch로 동작한다.
이문제를 해결하기 위해 Static Dispatch를 이용한 방법이 있다!
Static Dispatch을 이용한 성능 최적화 방법
- 상속, 오버라이딩이 필요 없는 클래스, 메서드, 프로퍼티에 final 키워드 사용하기
- 접근이 현재 파일로 제한되는 경우 private 키워드 사용하기
- WMO(Whole Module Optimization) 사용하기
상속, 오버라이딩이 필요 없는 클래스, 메서드, 프로퍼티에 final 키워드 사용하기
final 키워드를 사용하면 상속이 불가능하고 서브 클래스에서 오버라이딩이 불가능해진다.👍
접근이 현재 파일로 제한되는 경우 private 키워드 사용하기
private 키워드를 사용하는 경우 참조할 수 있는 곳이 현재 파일로 제한된다.
따라서 오버라이딩이 되는 곳이 없다면 final 키워드를 추론하여 Static Dispatch로 동작한다고 한다.
WMO(Whole Module Optimization) 사용하기
Whole Module은 모듈 전체를 하나의 덩어리로 컴파일하여 오버라이딩이 되었는지 확인 후 없으면 final 키워드를 자동적으로 붙여 사용하는데
만약 open, public 같은 키워드가 사용되는 경우 외부 모듈에서도 접근이 가능하기 때문에 WMO를 사용해도 Dynamic Dispatch로 동작한다고 한다.
어쨌든 Dynamic Dispatch 동작을 최소화 하면 앱 동작속도는 빨라진다!
# 참고한 사이트
'iOS > Swift 상식' 카테고리의 다른 글
Swift - Any vs AnyObject (0) | 2023.01.23 |
---|---|
Swift - Convenience init (0) | 2023.01.23 |
Swift - Copy On Write(COW) 동작 방식 (0) | 2023.01.23 |
iOS - Storyboard vs Code (0) | 2023.01.07 |
Swift - Class vs Struct (0) | 2022.12.31 |