Notice
Recent Posts
Recent Comments
Link
«   2025/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
Archives
Today
Total
관리 메뉴

J

[Swift] final 키워드와 Type에 따른 Dispatch 본문

Swift

[Swift] final 키워드와 Type에 따른 Dispatch

yujaehui 2024. 4. 3. 12:42

`final` 키워드와 Type에 따른 Dispatch

`final` 키워드는 클래스, 메소드, 프로퍼티 등에서 사용되며, 해당 클래스, 메소드, 프로퍼티 등을 상속하거나 오버라이드할 수 없도록 함.
→ 변경 불가능한 클래스, 메소드, 프로퍼티 등을 만들기 위한 키워드.

성능 최적화 및 코드 안전성 향상에 큰 역할을 하며, static/dynamic dispatch의 차이와 관련.


1. `final` 키워드의 특징

  • 클래스에 `final`을 사용하면 해당 클래스를 상속할 수 없음.
  • 메소드나 프로퍼티에 `final`을 사용하면 해당 메소드를 오버라이드할 수 없음.
  • `final`을 적용하면 컴파일 시점에 정적으로 호출이 결정되어 성능 최적화에 도움.

2. `final` 키워드 사용 예시

`final` 클래스

final class Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() {
        print("Animal sound")
    }
}

// 아래 코드는 컴파일 에러 발생 (Animal을 상속할 수 없음)
// class Dog: Animal { }

`final` 메소드

class Animal {
    final func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    // 아래 코드는 컴파일 에러 발생 (makeSound를 오버라이드할 수 없음)
    // override func makeSound() {
    //    print("Bark!")
    // }
}

3. Type에 따른 Dispatch

Dispatch는 메소드나 함수가 호출될 때 어떤 방식으로 해당 메소드를 찾고 실행할지를 결정하는 방법.

Swift에서는 크게 두 가지 종류의 Dispatch가 존재

  1. Static Dispatch (동적 디스패치): 컴파일 시점에 어떤 메소드가 호출될지 결정되는 방식.
  2. Dynamic Dispatch (정적 디스패치): 런타임 시점에 어떤 메소드가 호출될지 결정되는 방식.

4. Static Dispatch

Static Dispatch는 컴파일 시점에 메소드 호출이 결정되는 방식.

`final` 키워드가 적용된 클래스, 메소드, 프로퍼티, 구조체, 열거형 등.

런타임에 메소드 탐색 과정이 없으므로 성능이 더 빠르며 최적화가 가능.

예시

final class Animal {
    func makeSound() {
        print("Animal sound")
    }
}

let animal = Animal()
animal.makeSound() // 컴파일 시점에 makeSound 메소드가 결정됨

특징

  • 컴파일 시점에 메소드가 결정되므로 메소드 호출 속도가 빠름.
  • 상속과 오버라이드를 허용하지 않기 때문에 메모리 비용이 적음.

5. Dynamic Dispatch

Dynamic Dispatch는 런타임 시점에 메소드 호출이 결정되는 방식.

클래스를 상속하거나 메소드를 오버라이드할 수 있는 경우, 메소드 호출은 vTable을 통해 진행.

Objective-C 런타임을 사용하여 메소드 탐색을 수행하므로, 런타임에 어떤 메소드가 호출될지를 결정.

따라서 약간의 성능 오버헤드가 발생할 수 있음.

예시

class Animal {
    func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Bark!")
    }
}

let animal: Animal = Dog()
animal.makeSound() // 런타임 시점에 Dog의 makeSound가 호출됨

특징

  • 런타임 시점에 메소드가 결정되어 유연성이 높음.
  • 상속과 오버라이드를 지원하므로 객체 지향 프로그래밍에서 자주 사용.
  • 그러나 런타임에 메소드를 찾아야 하므로 성능이 Static Dispatch보다 느릴 수 있음.

6. vTable (Virtual Dispatch Table)

vTable은 클래스의 메소드를 관리하는 런타임 테이블로, 각 클래스마다 생성.

이 테이블은 클래스의 인스턴스 메소드 호출 시 어떤 메소드를 호출해야 할지를 런타임에 결정하는 데 사용.

작동 방식

  • 클래스가 생성될 때, 해당 클래스에 포함된 가상 메소드(virtual method)의 주소가 vTable에 기록.
  • 상속을 통해 메소드가 오버라이드되면, 부모 클래스의 메소드 대신 자식 클래스의 메소드 주소가 vTable에 저장.
  • Dynamic Dispatch를 사용하는 경우, 메소드가 호출될 때 런타임에 vTable을 참조하여 적절한 메소드가 호출.

예시

class Animal {
    func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Bark!")
    }
}

let animal: Animal = Dog()
// animal.makeSound() 호출 시, 런타임에 vTable에서 Dog의 makeSound 메소드 주소를 찾아 호출

특징

  • Dynamic Dispatch가 필요한 경우, 상속 구조에서 vTable을 통해 적절한 메소드를 동적으로 호출 가능.
  • 런타임에 vTable을 참조하는 오버헤드가 발생하지만, 이는 객체 지향 프로그래밍의 유연성을 위한 비용.
  • vTable은 클래스마다 존재하며, 상속된 클래스는 부모 클래스의 vTable을 확장하거나 오버라이드.

7. Static Dispatch vs. Dynamic Dispatch

  Static Dispatch Dynamic Dispatch
메소드 호출 시점 컴파일 시점에 메소드 호출이 결정 런타임 시점에 메소드 호출이 결정 (vTable 사용)
사용 대상 `final` 클래스, `final` 메소드, `static` 메소드, 구조체, 열거형 클래스의 오버라이드 가능한 메소드
성능 빠름 (메소드 탐색 없음) 상대적으로 느림 (런타임에 메소드 탐색)
유연성 상속과 오버라이드 불가능, 제한적 유연성 상속과 오버라이드 가능, 유연성이 높음
주요 사용 사례 성능 최적화가 중요한 경우, 상속 불필요한 경우 객체 지향 프로그래밍에서 상속과 오버라이드가 필요한 경우

8. final을 사용하는 이유와 성능 최적화 및 코드 안정성

성능 최적화

  • `final` 을 사용하면 메소드가 정적으로 디스패치되기 때문에 런타임 시 메소드 탐색 과정이 제거되고 메소드 호출 속도가 빨라짐
  • 상속과 오버라이드가 필요 없는 경우, `final`을 사용하여 런타임 오버헤드를 줄일 수 있음.

코드 안정성

  • 상속이 필요하지 않거나, 상속이 되면 문제가 발생할 수도 있는 클래스나 메소드 등에 `final`을 사용하면 오버라이드를 방지하고 코드의 일관성을 유지할 수 있음.
  • 특정 클래스나 메소드의 동작이 변경되지 않도록 보호할 수 있음.