k-coding
iOS ) Dispatch 본문
iOS ) Dispatch
이번에는 Swift에서 성능향상과 관련이 있는 Dispatch에 관해서 공부해보겠습니다.
Dispatch란 어떤 메서드를 호출할지 결정하여 실행하는 메커니즘으로
우선 Dispatch에는 크게 2가지 종류가 있는데요
1. Static Dispatch
- 컴파일 타임에 호출될 함수를 결정하여, 런타임 때 그대로 실행
- 컴파일 타임에 결정하기 때문에 성능상 이점을 가짐
2. Dynamic Dispatch
- 런 타임에 호출될 함수를 결정, 따라서 Class마다 함수 포인터들의 배열인 vTable이라는 것을 유지
- 하위 클래스가 메서드를 호출 시 vTable을 이용하여 실제 호출될 함수를 결정
- 런 타임에 결정하기 때문에 성능상 손해를 가짐
위 처럼 특성을 가진 2가지 Dispatch가 존재합니다.
그럼 어떤 상황에서 Static / Dynamic Dispatch를 사용할까요?
3. Static Dispatch 사용
위에 Dynamic Dispatch에서는 하위 클래스가 메서드를 호출 시 라는 말이 있는데,
즉 상속의 가능성이 있으면 Dynamic Disptach가 됩니다.
반대로 상속이 될 리가 없으면 Static Dispatch를 사용하겠습니다.
따라서 값 타입 ( Value Type )인 구조체와 열거형은 상속을 할 수 없다는 특성 때문에 Static Dispatch를 사용합니다.
그렇다면 상속이 아닌 확장 ( Extension )에서는 어떨까요?
마찬가지로 구조체나 열거형이 Extension된다 하더라도 이 자체는 상속이 아니고 상속의 가능성이 없기 때문에 여전히
Static Dispatch를 사용합니다.
struct Human {
func say() {
print("hahahah")
}
}
extension Human {
func tell() {
print("lalala")
}
}
let man: Human = .init()
man.say() // Static Dispatch
man.tell() // Static Dispatch
4. Dynamic Dispatch 사용
위에서 말했듯이 상속 가능성이 있는곳에서 오버라이딩의 가능성 때문에 사용되는데
그렇다면 참조 타입인 Class에서 Dynamic Dispatch가 사용된다.
class Human {
func say() {
print("hahahah")
}
}
class Man: Human {
override func say() {
print("lalala")
}
}
let me: Human = Man()
me.say() // lalala
이처럼 Override가 발생한 경우 me의 타입은 Human이지만 Man 인스턴스를 업캐스팅 하기 때문에
Man의 say를 참조합니다.
맨 처음 Dynamic Dispatch를 소개할 때 런 타임시 결정한다는게 이런것으로
컴파일러가 런 타임 시 상위 클래스의 메서드를 참조하는지 하위 클래스의 메서드를 참조하는지 확인해야합니다.
메서드 say는 override가 되지 않으면 상관 없지만 될 수 도 있다는 가능성이 존재하므로
vTable에 저장되어 함수포인터로 두고, 런타임 시점에 확인하여 어떤 메서드가 불리는지 결정하게 됩니다.
이 과정에서 vTable를 거치기 때문에 성능의 손해가 발생합니다.
참조 타입 class에서 확장을 할 때 Dispatch는 어떻게 될까요?
Class를 확장해서 매서드를 추가할 경우 서브 클래스에서 오버라이딩은 안됩니다!
class Human {
func say() {
print("hahahah")
}
}
extension Human {
func tell() {
print("lalala")
}
}
class Man: Human {
override func tell() {
ERROR!
}
}
확장을 통해 메서드를 추가할 경우 오버라이딩 자체가 불가능하기 때문에
메서드는 무조건 extension 메서드가 불리기 때문에 이때는 Dynamic Dispatch가 아닌 Static Dispatch가 사용됩니다.
이렇게 참조 타입에서 Dynamic Dispatch가 사용되는 것을 알아보았는데,
Dynamic Dispatch를 사용하는 경우가 한가지 더 있는데, 바로 Protocol에서 입니다.
Protocol은 기본적으로 메서드의 선언부만 제공하기 때문에, 실제 사용할 때 프로토콜 타입을 참조로만 사용할 경우,
해당 인스턴스 타입에 맞는 메서드를 호출해야 하기 때문에 Dynamic Dispatch를 사용합니다.
protocol Human {
func say()
}
struct Man: Human {
func say() {
print("man")
}
}
struct Woman: Human {
func say() {
print("woman")
}
}
// 아래 경우는 별 상관 없지만
let he: Man = .init()
he.say()
// Protocol을 타입으로 사용한 경우 때문에 Dynamic Dispatch
// Protocol 타입으로 해당 인스턴스 타입에 맞는 메서드를 확인해서 호출하기 때문
let she: Human = She()
she.say() -> Dynamic Dispatch
그렇다면 Protocol에서 확장이 사용되면 어떻게 될까요?
protocol Human {
func say()
}
extension Human {
func say() {
print("who")
}
}
class Man: Human {
func say() {
print("man")
}
}
class Woman: Human {
}
다음과 같이 Protocol에서 extesion을 통하여 default를 구현하였습니다.
var who: Human = Woman.init()
who.say() // who
who = Man.init()
who.say() // man
위 결과 처럼 Class 내에서 함수를 직접 구현한 Man의 경우 작성된 함수가 구현되지만
아무것도 작성하지 않은 Woman의 경우 확장을 통해 정의한 default값이 나오게 됩니다.
이 처럼 확장을 통하여 Protocol의 default 매서드를 구현하면 타입을 정확하게 단정 지을수 없기 때문에
Dynamic Dispatch로 동작합니다.
근데 경우가 하나 더 있습니다.
protocol Human {
}
extension Human {
func say() {
print("who")
}
}
class Man: Human {
func say() {
print("man")
}
}
class Woman: Human {
}
Protocol에 say()란 메서드가 없었는데 extension을 통하여 추가한 경우
1번, 2번 의 경우
Class Man에서 함수를 정의했든 말든 타입이 Human이 되어버리기 때문에 무조건 extension에서 구현한 함수가 실행됩니다.
// 1
var who: Human = Woman.init()
who.say() // who
// 2
who = Man.init()
who.say() // who
// 3
var ami: Man = .init()
ami.say(). // man
이러한 경우 함수의 인스턴스를 다시 확인할 필요가 없기 때문에 Static Dispatch가 동작됩니다.
'iOS > iOS' 카테고리의 다른 글
iOS ) Nib, Xib (0) | 2022.07.20 |
---|---|
Dispatch를 이용한 성능 향상 (0) | 2022.06.30 |
iOS ) UIWindow (0) | 2022.06.25 |
iOS ) Foundation Kit (0) | 2022.06.07 |
iOS ) UIKit (0) | 2022.06.06 |