본문 바로가기

iOS/Swift

Swift) Static Dispatch & Dynamic Dispatch (1/2)

 

 

안녕하세요 :) 소들입니다

제가 드디어 몇 주간 바빴던 현업을 마치고!!!! 여유로워져서!!!!

공부하고 싶은 내용이 생겨서 포스팅을 끄적여 봅니당 후훗

이번에 공부할 내용은 바로 Dispatch에 대한 것이에요!

 

음? Dispatch? 우리가 Thread 생성할 때 DispatchQueue 해서 쓰는 거아니야??

라고 생각하실 분도 있지만, 그거와는 조금 다른 내용이에요 :)

그렇다면 이 Dispatch라는 것이 뭔지에 대해 먼저 공부하고,

최종적으로 다다음 포스팅에서 이와 관련한 Swift 성능 개선에 대해 얘기해보려고 해요!

 

살짝 내용이 어려울 수 있고, 제가 틀렸을 수도 있으니 피드백은 언제나 환영이랍니다!

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

1. Static Dispatch & Dynamic Dispatch

 

 Dispatch란 어떤 메서드를 호출할 것인지를 결정하여, 그것을 실행하는 메커니즘이다

Swift에선 Static Dispatch와 Dynamic Dispatch 두 가지 방식이 있다

 

이렇게만 봐선 무슨 말인지 전혀 이해가 안 갈 것임!!ㅎㅎ

자, 그럼 조금 더 쉽게 설명하자면 

 

Dispatch라는 것은

내가 호출할 함수를 "컴파일 타임"에 결정하냐, "런타임"에 결정하냐에 따른 방식임!

이 두 가지 방식에 대해 조금 더 자세히 설명해보겠음

 

 

 

1-1. Static Dispatch (Direct Call)

 

"컴파일 타임"에 호출될 함수를 결정하여, 런타임 때 그대로 실행한다

컴파일 타임에 결정이 나기 때문에 성능상 이점을 가질 수 있다

 

 

 

1-2. Dynamic Dispatch (Indirect Call)

 

"런타임"에 호출될 함수를 결정한다

때문에 Swift에서는 클래스마다 함수 포인터들의 배열인 vTable(Virtual Methon Table)이라는 것을 유지한다

하위 클래스가 메서드를 호출할 때, 이 vTable 를 참조하여 실제 호출할 함수를 결정한다

이 과정들이 "런타임"에 일어나기 때문에 성능상 손해를 보게 된다

 

 

 

.

.

일단 두 가지 방식의 차이점에 대해서 대충만 알아도 됨!!!

Static Dispatch는 컴파일 시점에 함수를 결정해서 성능상 이점이 있고,

Dynamic Dispatch는 런타임 시점에 함수를 결정해서 성능상 손해가 있구나!

 

그러면 Dynamic Dispatch는 언제, 왜 사용하지?? 란 의문까지 들 수 있는데,

이제 Swift 에서 사용하는 대표적인 타입들

 

Reference Type, Value Type, Protocol

 

이 어떤 Dispatch를 사용하는지를 이제 같이 공부하면,

왜 함수를 결정하는 시점에 따라 Dispatch가 나뉘는 건지에 대해 알 수 있음 :)

 

 

 

 

2. Swift에서의 Dispatch

 

2-1. Reference Type에서의 Dispatch

 

Reference Type의 Class는 상속의 가능성이 있다

따라서, 서브 클래스에서 함수를 호출할 수 있기 때문에, Dynamic Dispatch를 사용한다

 

오..호 우리가 자주 사용하는 ClassDynamic Dispatch를 사용한다고 함

근데 그 이유가 상속 즉, "오버라이딩"의 가능성이 있기 때문임 

왜냐? 예제를 통해 보여주겠음

 

 

class Human {
    func sayHello() {
        print("Hello Human!")
    }
}
 
class Teacher: Human { }

 

 

이렇게 Human이란 클래스에서 sayHello라는 메서드를 만들었음

근데 Teacher이란 클래스가 Human 클래스를 상속받고 있지만, sayHello란 메서드를 오버라이딩 하진 않았음!

 

 

let sodeulHuman = Teacher()
sodeul.sayHello()               // Hello Human!

 

 

이 경우, 내가 Human이란 타입으로 Teacher 인스턴스를 가리키고 있을 때,

이때는 문제가 되지 않음 왜냐? sayHello는 오버라이딩 되지 않았기 때문!!

무조건 sodeul의 타입인 Human 클래스의 sayHello가 불릴거거등!!

 

 

class Human {
    func sayHello() {
        print("Hello Human!")
    }
}
 
class Teacher: Human {
   override func sayHello() {
        print("Hello Teacher!")
    }
}

 

 

근데 만약 위처럼 하위 클래스에서 sayHello라는 메서드를 오버라이딩을 하면.....

 

 

let sodeulHuman = Teacher()
sodeul.sayHello()               // Hello Teacher!

 

 

sodeul이란 변수의 타입은 Human이지만 Teacher 인스턴스를 업캐스팅 해서 가리키고 있기 때문에,

이때는 Human 클래스의 sayHello를 참조해야 하는 게 아니라

Teacher이란 클래스의 sayHello를 참조해야 함

 

이처럼 컴파일러는 클래스의 메서드가 

하위 클래스에서 오버라이딩이 될 경우를 대비해,

상위 클래스의 sayHello를 참조해야 하는지,

하위 클래스의 sayHello를 참조해야 하는지를 확인하는 작업을 해야함

언제? 런타임에!! 

 

따라서, sayHello라는 함수는

각 클래스마다 가지고 있는 vTable이란 것 안에 함수 포인터로 두고,

실제 런타임 시점에 이 vTable을 사용하여 어떤 메서드가 불리는지를 결정해버리는 것임

 

물론 첫 번째 예제처럼 sayHello가 아무 곳에서도 오버라이딩 되지 않을 수도 있지만,

어찌 됐건 클래스는 오버라이딩이 될 수 있다는 "가능성"이 있기 때문에

실제 오버라이딩이 된지 안 된지는 따지지 않고 무조건 vTable을 확인해서 참조함

 

 vTable을 참조한단 말이 좀 어려울 거 같은데!!! 좀 더 자세히 얘기해보자면,

모든 하위 클래스에는 고유한 상위 클래스의 vTable 복사본이 있고,

이 vTable에는 이 클래스에서 오버라이딩 한 모든 메서드들을 가리키는 함수 포인터가 있음

(만약 오버라이딩 하지 않은 메서드가 있다면, 상위 클래스의 함수 포인터가 그대로 들어있겠징..?...😱)

만약 하위 클래스가 새 메서드를 추가하면 해당 메서드 포인터가 vTable의 끝에 추가 됨

 

따라서 Teacher이란 인스턴스에서 sayHello를 호출할 경우,

런타임 시점에 Teacher이란 클래스의 vTable을 탐색하여,

실제 불릴 sayHello의 함수 포인터를 찾아 실행시킨다는 것임

 

이처럼, 런타임 과정에 해당 클래스의 vTable에서 함수를 찾아 메모리 주소를 "읽고",

그 주소로 "점프"해야 하기 때문에 두 개의 추가 명령이 필요해서 성능상 손해를 보는 것임!

 

위에서 그럼 성능 안 나오는 Dynamic Dispatch는 왜 쓰나?? 란 생각이 들 수 있는데,

OOP에서, 오버라이딩을 하기 위해서 Dynamic Dispatch는 필수적인 것이었던 것임

 

근데, 그러면 나는 상속 안 할 건데 자꾸 함수 호출 때마다 Dynamic Dispatch로 동작하면 너무 성능 손해 아님?

라고 생각 할 수 있는데, 맞음맞음!!!

따라서 상속이 필요 없는 Class에서 Static Dispatch로 동작하게 하여

성능 향상을 시키는 것이 바로 다다음 포스팅의 내용임! :)

 

 

 

2-2. Value Type에서의 Dispatch

 

Value Type인 구조체, 열거형은 상속을 할 수 없다는 특징 때문에

오버라이딩이 될 가능성이 없고, 따라서 Static Dispatch를 사용한다

 

위에서 Dispatch는 오버라이딩의 개념 때문에 Dynamic Dispatch로 동작했는데,

Value Type은 상속이 안 되니까 Static Dispatc가 가능한 것임

 

 

struct Human {
    func sayHello() {
        print("Hello Human!")
    }
}

 

 

구조체의 경우 어디서 sayHello란 함수를 불러도,

Human이란 구조체 안의 함수가 불릴 것이 보장되기 때문에

런타임에 따로 추적할 필요가 없어서 컴파일 타임에 결정이 되는 것!!!

 

 

 

2-3. Protocol에서의 Dispatch

 

프로토콜은 기본적으로 메서드의 선언부만 제공하기 때문에,

실제 사용할 때 프로토콜 타입을 참조로만 사용할 경우,

해당 인스턴스 타입에 맞는 메서드를 호출해야 해서 Dynamic Dispatch를 사용한다

 

흠... 이부분이 조금 어려웠었는데

실제 사용할 때 프로토콜 타입을 참조로만 사용할 경우!!!

이게 먼말인지 모르겠었는데!!! 내가 이해한 바로는!!!!

 

   

protocol Human {
    func description()
}
 
struct TeacherHuman {
    func description() {
        print("I'm a teacher")
    }
}
 
struct StudentHuman {
    func description() {
        print("I'm a student")
    }
}

 

 

자, 위와 같은 Human이란 프로토콜이 있고,

Teacher, Student라는 구조체에서 Human이란 프로토콜을 준수하고 있음

 

이때, 만약 다음과 같이 사용할 경우에는

 

 

let teacherTeacher = .init()
teacher.description()           // I'm a teacher
 
let studentStudent = .init()
student.description()           // I'm a student

 

 

이 경우엔 해당 구조체에 한해서만 description이 불릴 것이기 때문에,

protocol 또한 Static Dispatch로 작동해도 될 것 같지만

 

그러나!! 만약 다음과 같이 Protocol을 타입으로 사용해서

해당 인스턴스들의 description이란 메서드를 실행할 경우,

 

  

var humanHuman = Teacher()
human.description()             // I'm a teacher
 
human = Student()
human.description()             // I'm a student

 

 

아까 Human이란 클래스 타입에서 상위 클래스인지 하위클래스인지 확인 했던 작업처럼

프로토콜 타입으로 해당 인스턴스 타입에 맞는 메서드를 확인하여 호출해주어야 하기 때문에,

Static Dispatch처럼 description이 호출되는 타입을 지정해놓을 수가 없어서

이때는 Dynamic Dispatch를 사용하는 게 아닐까!!!

 

하고 이해를 했는데...ㅎ;

혹시 틀린 부분이나 잘못 이해한 거라면 피드백 바람돠

쨌든 프로토콜 또한 Dynamic Dispatch를 사용하고,

이 때 가지는 vTable을 특별히 Witness Table이라고 한다고 함

 

 

 

 

 

 

.

.

.

일단 첫 번째 포스팅은 여기서 끝!!!

거의 다 다루긴 했는데, 위에서 공부한

Value Type, Reference Type, Protocol을 Extension 할 경우 Dispatch 방식이 달라질 수 있기 때문에,, 

이 내용은 꽤 어렵고 길어질 거 같아서 다음 포스팅에서 이어서 하도록 하겠습니당 :)

이 두 개의 Dispatch 포스팅이 끝나면 공부한 것을 기반으로 Swift 성능 향상에 대해서도 같이 봐보자구요!!!

 

어렵군.. 어려워... 틀리 내용이 있을수도 있어요 여러분..

잘못된 내용이나 오타는 언제든 피드백 주세요!

 

 

 

 

 



Calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
최근 댓글
Visits
Today
Yesterday