Swift-왜 Final을 사용할까?

8725 단어 swiftswift

안녕하세요!! :) 제제로입니다.
오늘은 Final에 대해서 이야기해보려고 합니다.
저는 이 Final을 인터넷 강의를 들으면서 처음 접했는데 그때 당시에 강사님이 상속 하지 않는 class 앞에는 Final 키워드를 붙이는 게 좋다고 하셔서 아무 생각 없이 사용하고 있었습니다. 갑자기 왜 Final을 사용하면 왜 좋은지 궁금해서 한번 알아 보도록 합시다 ㅎㅎ


💡 왜 Final , Private를 사용하면 성능이 향상될까?

그 이유는 Reducing Dynamic Dispatch !!!

Dispatch는 어떤 메소드를 호출할 것인가를 결정하는 그것을 실행하는 과정입니다.

Dispatch의 종류

  • Static Dispatch
    • 컴파일 시점에 어떤 메소드가 사용될지 명확하게 결정되는 것
  • Dynamic Dispatch
    • 런타임 시점에 어떤 메소드가 실행될지 결정되는 것, 컴파일 시점에서는 어떤 함수가 실행될 지 모른다.
    • Swift 에서는 class 마다 vtable을 갖고 있고 이를 참조하면서 함수가 호출되기 때문에 이에 따른 overhead 발생
    • v table?? → Virtual Method Table, 가상 메소드 테이블, 메소드 오버라이딩에 따라 실행 시점(런타임)에 어떤 메소드를 실행할지 결정하는 동적 디스패치를 지원하기 위해 프로그래밍 언어에서 사용하는 매커니즘 (함수 포인터들의 배열로 구성되어 있다고 합니다.)

💡 Static VS Dynamic Dispatch

// Static Dispatch
struct HelloStruct {
  func printHello() {
    print("hello")
  }
}
let helloStruct = HelloStruct()
helloStruct.printHello()

struct는 Value Type이기 때문에 다른 곳에서 상속이 되지 않는다. 따라서 컴파일 시점에서 helloStruct.printHello() 호출이 항상 HelloStruct의 printHello를 호출한다는 사실이 명확하므로 컴파일 시점에서 결정되는 Static Dispatch로 작동한다. 또한 Value Type에서는 확장(Extension)에서도 Static Dispatch로 작동한다.

// Dynamic Dispatch
class HelloClass {
  func printHello() {
    print("hello")
  }
}

class HelloOtherClass: HelloClass { }

let helloClass: HelloClass = HelloOtherClass()
helloClass.printHello()

class는 Reference Type이므로 다른 곳에 상속할 수 있다. 따라는 호출한 print Hello라는 메소드가 어떤 객체에서 호출되는지 컴파일 시점에서는 정확히 알 수 없다. 따라서 컴파일러는 런타임시점에서 v table을 조회하여 어떤 인스턴스에서 메소드가 실행되는지 판단하게 하도록 참조 형식으로 두는 것이다. 이것이 Dynamic Dispatch이다.

💡 Increasing Performance by Reducing Dynamic Dispatch

스위프트는 여러 method 혹은 properies를 슈퍼클래스로부터 override 할 수 있습니다. 이는 즉 프로그램이 런타임시에 indirect call(주소를 통해 함수 호출) & indirect access(간접 접근)를 통해서 어떤 method 혹은 property를 호출할지를 정하는 것을 말합니다. 이를 다이나믹 디스패치라 부르고 이런 indirect usage를 사용할 때마다 overhead가 발생합니다. 따라서 static Dispatch를 사용하여 성능을 향상하는 방법을 사용하게 됩니다.

  • 상속, 오버라이딩 될 필요가 없는 클래스, 메서드, 프로퍼티에 final 키워드 붙이기

final를 붙이면 클래스의 경우 상속이 불가능하고 메소드나 프로퍼티에 붙을 경우네는 하위 클래스에서 오버라이팅 할 수 없기 때문에 static Dispatch로 작동하게 된다.

final class Human {
    var name: String = ""
    func sayHello() {
        print("Hello Human!")
    }
}
 
class Teacher: Human { }    // error! Inheritance from a final class 'Human'
  • 접근이 현재 파일로 제한되는 경우 private 키워드 붙이기

private 키워드를 붙일 때 참조할 수 있는 곳은 블록으로 제한된다.
따라서 컴파일러는 private 키워드가 참조될 수 있는 곳에서 오버라이딩이 되는지 안되는지를 알아서 판단한다. 그리고 만약 오버라이딩이 되는 곳이 없다면 스스로 final 키워드를 추론해서 Static Dispatch로 동작한다.

class Human {
    private var name: String = "" // Dynamic Dispatch
    private var alias: String = "" // Static Dispatch
    var age: Int = 0
    
    class Sodeul: Human {
        override var name: String {
            didSet {
                print("이름 바꼈다!")
            }
        }
    }
}
  • WMO(Whole Module Optimization) 사용하기

WMO?? → 모듈 전체를 하나의 덩어리로 컴파일 하여 internal level에 대해서 오버라이딩이 되는지 안 되는지를 추론 할 수 있게 되고 오버라이딩 되지 않을 경우, 내부적으로 final를 붙힌다.

이게 무슨 말 이냐면 Swift는 기본적으로 컴파일할 때 모듈 내의 파일을 하나씩 컴파일합니다. 즉 클래스가 상속 되는지 컴파일 시점에 확인하고 싶어도 다른 파일에 상속된 클래스가 있을 수도 있기 때문에 final을 추론할 수가 없습니다. 하지만 WMO로 설정하면 모듈 전체를 확인하고 컴파일 하므로 상속이 되지 않은 클래스를 final로 내부적으로 붙여서 Static Dispatch로 동작하게 합니다. 이것이 가능한 이유는 Swift 클래스의 기본 접근 제어자가 internal이기 때문에 가능합니다.

만약 open 키워드를 붙일 경우, 외부 모듈에서도 접근할 수 있기 때문에

이때는 WMO를 사용하여도 Dynamic Dispatch로 동작한다고 함


마무리

앞으로 상속하지 않는 Class는 Final을 붙여주고 다른 클래스에서 참조하지 않는 프로퍼티나 메소드는 private로 선언하는 습관을 들여야겠다.

감사합니다 😊
건강한 지적은 언제나 환영합니다

좋은 웹페이지 즐겨찾기