[Swift] Method Dispatch


Method Dispatch


매서드 디스패치

  • 클래스, 프로토콜의 메서드가 실행되는 방식

Direct Dispatch (직접 / Static)

  • 컴파일 시점에 코드 자체에 함수의 메모리 주소 삽입 또는 함수의 명령 코드를 해당 위치에 코드를 심음 (in-line)
  • 가장 빠른 속도 (0.0 - 2.13ns)
  • 밸류 타입(구조체/열거형)에 사용
  • 상속 / 다형성의 장점을 누릴 수 없음
struct MyStruct {
    func method1() { print("Struct - Direct method1") }  // 90~99 메모리 주소에 저장
    func method2() { print("Struct - Direct method2") }  // 100~109 메모리 주소에 저장
}

let myStruct = MyStruct()
myStruct.method1() // 메모리 주소 90 호출
myStruct.method2()  // 메모리 주소 100 호출

Table Dispatch (동적 / Dynamic)

  • 함수의 포인터를 배열 형태 보관 후 실행
  • 중간 속도 (3.23ns)
  • 클래스 / 프로토콜에서 사용
    • Virtual Table : 클래스 테이블
    • Witness Table : 프로토콜 테이블
class FirstClass {
    func method1() { print("Class - Table method1") }  // 110~119 메모리 주소에 저장
    func method2() { print("Class - Table method2") }  // 120~129 메모리 주소에 저장
}

// [110, 120]   -> 배열 형태로 저장
// ====================================================
//  func method1() { print("Class - Table method1") }      ->   110
//  func method2() { print("Class - Table method2") }      ->   120
// ====================================================

// 자식클래스에서 테이블을 따로 보유
class SecondClass: FirstClass {
    override func method2() { print("Class - Table method2-2") }  // 130~139 메모리 주소에 저장
    func method3() { print("Class - Table method3") }             // 140~149 메모리 주소에 저장
}

// [110, 130, 140]   -> 배열 형태로 저장
// ====================================================
//  func method1() { print("Class - Table method1") }      ->   110
//  func method2() { print("Class - Table method2-2") }    ->   130
//  func method3() { print("Class - Table method3") }      ->   140
// ====================================================

let first = FirstClass()
first.method1() // 메모리 주소 110 호출
first.method2() // 메모리 주소 120 호출

let second = SecondClass()
second.method1() // 메모리 주소 110 호출
second.method2() // 메모리 주소 130 호출
second.method3() // 메모리 주소 140 호출

프로토콜 - Witness Table

protocol MyProtocol {
    func method1()    // 요구사항 - Witness Table
    func method2()    // 요구사항 - Witness Table
}

extension MyProtocol {
    // 요구사항의 기본 구현 제공
    func method1() { print("Protocol - Witness Table method1") }
    func method2() { print("Protocol - Witness Table method2") }
    
    // 필수 요구사항은 아님 -> Direct Dispatch
    func anotherMothod() {
        print("Protocol Extension - Direct method")
    }
}

클래스 - Virtual Table

class FirstClass: MyProtocol {
    func method1() { print("Class - Virtual Table method1") }
    func method2() { print("Class - Virtual Table method2") }
    func anotherMothod() { print("Class - Virtual Table method3") }
}

// ==================================================================
// [Class Virtual Table]
// - func method1() { print("Class - Virtual Table method1") }
// - func method2() { print("Class - Virtual Table method2") }
// - func anotherMothod() { print("Class - Virtual Table method3") }
// ==================================================================

// ============================================================
// [Protocol Witness Table]
// - func method1() { print("Class - Virtual Table method1") }  요구사항 -> 우선 반영
// - func method2() { print("Class - Virtual Table method2") }  요구사항 -> 우선 반영
// ============================================================

let first = FirstClass()
first.method1()           // Class - Virtual Table method1
first.method2()           // Class - Virtual Table method2
first.anotherMothod()     // Class - Virtual Table method3

let proto: MyProtocol = FirstClass()
proto.method1()           // Class - Virtual Table method1  (Witness Table)
proto.method2()           // Class - Virtual Table method2  (Witness Table)
proto.anotherMothod()     // Protocol Extension - Direct method

Message Dispatch (메세지)

  • 상속 구조를 모두 훑은 뒤에, 실행할 메서드 결정
  • 가장 느린 속도 (5.82ns)
  • 주로 Objective-C 클래스에서 사용
  • Objective-C 런타임에 의존
// 예전 Objective-C 에서 사용하던 방식
// 방식에 대한 이해만 하고 굳이 암기할 필요는 없음

class ParentClass {
    @objc dynamic func method1() { print("Class - Message method1") }
    @objc dynamic func method2() { print("Class - Message method2") }
}

// =====================================================
//  func method1() { print("Class - Message method1") }
//  func method2() { print("Class - Message method2") }
// =====================================================

class ChildClass: ParentClass {
    @objc dynamic override func method2() { print("Class - Message method2-2") }
    @objc dynamic func method3() { print("Class - Message method3") }
}

// =======================================================
//  super class                                     부모 클래스를 찾아가서 주소 확인
//  func method2() { print("Class - Message method2-2") }  재정의한 메서드는 다시 주소가짐
//  func method2() { print("Class - Message method3") }
// =======================================================

let child = ChildClass()
child.method1()
child.method2()
child.method3()

스위프트가 함수를 실행 하는 방법

구분본체
(Initial Declaration)
Extension비고
Value Type
(Struct)
Direct DispatchDirect Dispatch 
ProtocolTable Dispatch
(Witness Table)
Direct Dispatch
(메서드 디폴트 구현 제공)
본체의 요구사항 메서드를
Witness Table로 구현
(프로토콜을 채택한 타입마다 테이블을 만듦)
ClassTable Dispatch
(Virtual Table)
final 키워드 (상속 불가) -> Direct
Direct Dispatch
(상속시 재정의 불가 원칙)
@objc dynamic -> Message
@objc dynamic 키워드를 통해,
Message Dispatch로 바뀌면
extension내의 메서드 재정의 가능
@objc dynamicMessage DispatchMessage Dispatch 

참고

Ellen Swift Class






© 2021. by hminkim