우선 컴파일 타임에 어떤 함수를 실행할지 알 수있다는건
struct Some {
var number = 1
func Something() {}
}
이런 함수가 “유일”하다는게 아닐까(유일하다는 뜻은 재정의가 되지 않는다는 뜻이다) 왜냐면 재정의가 되버리면 같은 함수이름을 가진 다른함수가 생성되게 된다.
함수는 기본적으로 명령어의 묶음이고 명령어는 코드영역에 저장된다 예를 들어서 Something이라는 함수가 코드의 10번째줄부터 19번째줄까지의 명령어의 묶음이라면 저 함수가 유일하다면 그냥 저 함수가 실행되면 그냥 10번째줄로 가서 하라는 명령을 수행하기만 하면된다.
let some = Some()
some.Something()
바로 위의 코드에서 some이라는 변수안에 number라는 변수(1을 가지고 있는)와 함수의 실행주소를 가지고 있는 Something이 존재하게 된다. 인스턴스에 Something을 실행하면 바로 함수를 실행할 수있게 된다
다음은 런타임에 어떤함수를 실행할지 알 수 있다는 부분인데, 이부분을 이해하려면 간단한 상속예제가 필요하다.
Aclass {
let number = 1
func method() {}
}
Bclass: Aclass {
override func method() {}
func method2() {}
}
위의 코드를 설명해보면 Aclass도 method라는 함수를 가지고 있고 Bclass도 method를 가지고 있다. 하지만 Bclass의 method는 이름은 같지만 재정의한 메서드이기때문에 서로다른 함수의 실행주소를 가지고 있다. 예를들어 Aclass의 method라는 녀석은 20번째줄에서 시작한다고 하고 Bclass의 method는 30번째줄에서 시작한다고 가정을하자
클래스의 경우에는 컴파일 타임에 각 클래스의 함수실행주소를 array형태로 저장한다
즉, Aclass의 함수실행 주소모음 = [20번째줄], Bclass의 함수실행 주소모음 = [30번째줄, 40번째줄(method2)]이런식으로 말이다.
class.method()
예를 들어서 이런 코드가 있다고 가정해보자 method()함수를 실행시켰는데 이 method함수는 20번째줄에서 시작하는 함수일까 30번째줄에서 시작하는 함수일까? 이걸 모르기때문에 컴파일 시점에서 method라는 함수는 몇번째줄에서 시작하는 함수다라는걸 정해놓을 수 없다.
class가 어떤타입으로 선언되었는지를 확인한후(1), 클래스의 인스턴스로 가서 그 주소모음에서 method의 시작점을 확인하고 함수를 실행(2)하게 된다.
구조체나 열거형같은 valueType의 경우 함수자체가 재정의할 일이 없기 때문에 그냥 method자체가 몇번째줄에서 시작할지를 컴파일 시점에서 정해버릴수있지만
재정의가 가능한 클래스의 경우 같은 함수라도 재정의를 통해 함수의 시작점이 달라지기 때문에 그 함수가 몇번째줄에서 시작할거라는걸 100퍼센트 확실하게 알 수 없다. 그렇기 때문에 인스턴스의 타입을 확인한 후, 그 타입의 함수실행 주소모음에서 함수의 실행시점을 알 수 밖에없다.
클래스의 경우 컴파일타임에 각 함수의 실행시점을 모아놓은 배열이 생성되고 런타임에 타입을 확인한 후 인스턴스를 거쳐 함수를 실행하기 때문에 단계가 늘어나 Direct Dispatch보다 조금 느릴 수 밖에 없다.
💡 class에 final키워드를 붙이면 상속이 불가능하고 상속이 불가능하다라는 뜻은 메서드의 재정의가 불가능하기때문에 함수가 유일하게 존재할 수 있고 따라서 자동으로 컴파일 시점에서 함수의 실행시점이 고정된다. Direct Dispatch로 함수를 실행시킬수 있게 된다.