타 언어에서 이미 final을 접해보기는 했지만 자세히 알아본 적은 없어서 이번에는 final에 간단하게 알아봤습니다!
❓ final
final은 Apple이 제공하는 Document의 상속(Inheritance) 파트에서 찾아볼 수 있습니다.
이에 따르면, final은 class 이외에도 method, property, subscript등의 앞에 붙여 사용 가능하다고 하네요!
🧐 그렇다면 final은 어떤 기능을 하는 걸까요?
final은 단어 그대로 ‘최종!’이기 때문에 final 키워드가 붙은 method, property, subscript는 서브클래스에서 오버라이드가 불가능하게됩니다. 그렇기 때문에 만약 final 키워드가 붙은 무언가를 상속받아 오버라이드하려고 하면 컴파일 타임 에러가 발생하게 됩니다.
class의 경우에도 마찬가지로 final class를 상속받아오려고 하면 컴파일 타임 에러가 발생하게 됩니다.
playground에서 final class를 상속받아오는 간단한 코드로도 final class는 상속이 불가하다는 아래와 같은 에러 메시지가 출력되는 것을 확인할 수 있습니다!
error: inheritance from a final class 'class명’
final class book {
var name: String = ""
}
class scienceBook: book {
var bookName: String
}
// error: inheritance from a final class 'book'
🧐 그럼 final을 사용해서 좋은 점은 뭘까요 ?
cocoacasts의 Bart Jacobs의 글을 참고해 보면 final을 사용했을 때에 세 가지 장점이 있습니다.
- 명료함
final 키워드를 사용하면 해당 클래스에 서브클래스가 없다는 것을 쉽게 알 수 있어 해당 클래스를 이용한 서브클래스가 없다는 것을 쉽게 알 수 있었다고 합니다.
- 오용 방지
method 또는 property에 final 키워드를 이용하여 구현하면 서브클래스를 설계할 때 무작정 사용하지 않도록 고정시켜 둘 수 있다는 점에서 오용을 방지할 수 있었다고 합니다.
- 성능
final로 표시된 entity의 경우, 컴파일러가 코드를 최적화하는데 도움이 돼 런타임 시간이 단축되고 성능이 향상된다고 합니다.
하지만 사실 이러한 성능차가 크지는 않아 우리가 느끼기에는 미미한 수준이라고 하네요..!
그래도 일단 성능에는 영향을 준다는 게 사실인데, 키워드 하나로 어떻게 성능이 향상되는 결과를 보이는건지 궁금하지 않으신가요?
🧐 키워드 하나로 어떻게 성능이 향상된다고요 ?
final 키워드로 인한 성능을 야기하기 위해서는 Dynamic Dispatch와 Static Dispatch에 대해 먼저 알아야 합니다!
둘을 아주 간단히 정리해 보면 다음과 같습니다.
- Dynamic Dispatch
- 상속, 오버라이드의 가능성을 가짐
- Swift의 기본 방식
- 런타임 내에 어떤 메서드와 프로퍼티를 호출할지 결정(vTable 이용)
- Static Dispatch
- ⇒ 런타임 시간 감소
- 컴파일을 할 때 어떤 메서드와 프로퍼티를 호출할 지 결정
*) Dispatch : 어떤 메서드를 호출했을 때, 실제로 어떤 메서드가 실행될지 결정하는 것
여기서 명확하게 드러나는 둘의 차이는 메서드 및 프로퍼티의 호출 타이밍입니다.
Dynamic Dispatch의 경우, 상속과 오버라이딩의 가능성을 염두에 두고 있어 어떤 인스턴스에서 메서드를 호출하게 되면 vTable이라는 테이블에서 해당 메소드를 구현하고 있는 코드를 찾아 불러옵니다.
하지만 Static Dispatch의 경우 상속과 오버라이딩의 가능성을 가지고 있지 않기 때문에 해당 과정을 거치지 않는다는 차이가 있습니다.
결론적으로, final을 사용해 준다면 실행했을 때 vTable을 거치는 시간이 사라지며 성능적인 이점을 가지게 되는 것이라고 합니다!
✏️ 오늘의 요약
- final은 클래스, 변수 등의 앞에 사용할 수 있다
- final은 상속과 오버라이딩을 막는 역할을 한다
- 코드가 명료해짐과 동시에 무분별한 사용을 막을 수 있으며, 미세한 성능적 이점을 가진다
→ 왜? 런타임에서 필요한 요소들을 호출하는 것이 아니라 컴파일 시점에 호출할 메서드와 프로퍼티들을 정해두기 때문이다
📚 참고
- https://docs.swift.org/swift-book/documentation/the-swift-programming-language/inheritance/#Preventing-Overrides
- https://jellysong.tistory.com/122
- https://cocoacasts.com/swift-fundamentals-what-are-the-benefits-of-the-final-keyword-in-swift
- https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#reducing-dynamic-dispatch
- https://itllbegone.tistory.com/10