디스패치(Dispatch)란 어떤 메서드를 호출할 것인가를 결정하여 그것을 실행하는 매커니즘을 뜻합니다.
디스패치의 종류에는 정적(Static) 디스패치와 동적(Dynamic) 디스패치가 있습니다.
정적 디스패치 방식은 어떤 메서드를 호출해야 하는지 컴파일 시점에 명확히 알 수 있는 방식이고, 동적 디스패치 방식은 어떤 메서드를 호출해야 하는지 런타임 시점에 알 수 있는 방식입니다.
class Person {
func walk() { }
}
class Student: Person {
override func walk() { }
}
class Programmer: Person { }
let joo: Person = Person()
let miji: Student = Student()
let mai: Programmer = Programmer()
let people: [Person] = [joo, miji, mai]
for person in people {
person.walk()
}
위의 코드에서 joo
, miji
, mai
는 모두 Person
입니다. Person
이거나 Person
을 상속받았기 때문이죠.
맨 아랫줄의 for
구문에서 [Person]
Array에 속한 인스턴스 각각에게 walk()
메서드를 호출한다면, 컴파일타임에는 Person
의 walk()
를 호출해야 할지, 오버라이드 된 Student
의 walk()
를 호출해야할지 결정할 수 없습니다. 런타임에서야 비로소 for
구문을 실행하면서 person
인스턴스가 진짜 어떤 타입인지 확인한 후에 적절한 walk()
를 호출할 수 있기 때문입니다. 이렇게 런타임에 호출할 메서드를 결정하는 방식을 동적 디스패치라고 합니다. 런타임에 호출할 메서드를 결정해야 하기 때문에 실행속도에 불리합니다. 하지만 인스턴스의 다형성 구현을 위해서는 꼭 필요한 방식이기도 하죠.
struct Robot {
func walk() { }
}
let pica: Robot = Robot()
let chu: Robot = Robot()
for robot in [pica, chu] {
robot.walk()
}
반면에 구조체는 상속기능이 없기 때문에 오버라이드 가능성이 없으므로, 항상 Robot
의 walk()
를 호출해도 된다는 사실이 자명합니다. 그렇기 때문에 컴파일타임에 어떤 메서드를 호출할지 명확히 알 수 있습니다. 이렇게 컴파일타임에 어떤 메서드를 호출할지 결정하는 방식을 정적 디스패치라고 합니다. 컴파일타임에 호출할 메서드가 정해지고 컴파일러가 런타임 코드를 최적화 할 수 있기 때문에 런타임 성능이 더욱 뛰어납니다.
그렇기 때문에 런타임 성능을 최적화하려면 동적 디스패치를 줄이고, 정적 디스패치를 최대한 활용하는 것이 좋습니다.
final
을 꼭 명시하기오버라이드 하지 않을 요소에 final
을 명시하면 런타임에 호출할 메서드를 찾지 않아도 되기 때문에 정적 디스패치가 됩니다.
class Person {
func walk() { }
final func run() { }
}
class Student: Person {
override func walk() { }
final func study() { }
}
class Programmer: Person { }
let joo: Person = Person()
let miji: Student = Student()
let mai: Programmer = Programmer()
let people: [Person] = [joo, miji, mai]
for person in people {
person.walk() // 동적 디스패치
person.run() // 정적 디스패치
}
miji.study() // 정적 디스패치
final class Dog {
func sleep() { }
func eat() { }
}
let puppy: Dog = Dog()
puppy.sleep() // 정적 디스패치
puppy.eat() // 정적 디스패치
private
와 fileprivate
사용하기private
또는 fileprivate
키워드를 통해 파일 외부 또는 자식 클래스로 구현부 요소가 드러나지 않으면 오버라이드의 가능성을 배제하기 떄문에 컴파일러가 정적 디스패치할 수 있습니다. private
또는 fileprivate
를 붙여 선언하면 컴파일러가 알아서 final
키워드를 붙여주기 때문에 정적 디스패치가 가능합니다.
private class Cook {
func cook() { }
}
class Magician {
fileprivate func magic() { }
}
let cook: Cook = Cook()
let magician: Magician = Magician()
cook.cook() // 정적 디스패치
magician.magic() // 정적 디스패치
위 코드에서는 Cook
과 Magician
의 각각의 메서드에 오버라이드 구현이 없으므로 정적 디스패치로 동작합니다.
internal
사용하기WMO를 사용하면 컴파일러가 모듈의 소스코드를 한 번에 컴파일합니다(WMO를 잘 모르신다면 앞의 WMO 레슨을 참고해주세요). 그렇기 때문에 개별 요소가 어디에 쓰이는지 모듈 전체에서 파악할 수 있고 그를 통해 최적화 할 수 있는 가능성이 높아집니다. 개별 요소를 internal
선언을 하면 모듈 밖으로 드러나지 않으므로, 컴파일러는 모듈 밖에서 오버라이드 가능성이 없는 요소로 판단하고 final
로 최적화 할 수 있습니다. 최적화가 이루어지면 정적 디스패치를 할 수 있죠.
스위프트 언어의 기본 접근수준(access level)은 internal
이기 때문에 사실 크게 신경쓸 부분은 아닙니다. 런타임 성능을 최적화하고 싶다면 WMO를 활성화하는 것이 좋겠습니다.