[번역] Method Dispatch

도윤·2022년 10월 9일
0

iOS

목록 보기
6/11

아래의 내용을 번역한 내용입니다.
https://medium.com/@bakshioye/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d

Method Dispatch

Static dispatch는 value 타입과 reference타입을 모두 지원한다.

그러나 Dynamic Dispatch는 refence type만을 지원한다. reference type은 상속을 위해 dynamic dispatch가 필요하고 value type은 상속을 지원하지 않는다.

Dispatch techniques에는 static, dynamic뿐만 아니라 4가지를 지원한다.

  1. Inlin(Fastest)
  2. Static Dispatch
  3. Virtual Dispatch
  4. Dynamic Distpach(Slowest)

Static vc Dynamic

기본적으로, Objective-c는 Dynamic Dispatch를 지원한다. 다형성의 형태로 프로그래머에게 유연성을 제공한다. 기존에 존재하는 method와 변수들을 서브클래싱하고 오버라이딩하는 것은 훌륭하지만 비용이 든다.

Dynamic Dispatch는 런타임 오버헤드의 비용으로 언어의 표현력(expressivity)를 높인다. Dynamic Dispatch 경우에 컴파일러가 특정 메서드의 구현을 확인하기 위해 감시 테이블(virtual table)이라고 불리는 내부 정보를 확인해야 한다. 컴파일러는 superclass의 구현부를 참조하는지, subclass를 참조하는지 판단해야 된다.

모든 객체는 runtime에 메모리에 할당되기 때문에 컴파일러는 오직 런타임에만 확인을 할 수 있다.

그러나 static dispatch는 위와 같은 문제점을 갖지 않는다. 컴파일러는 컴파일 타임에 어떤 메소드 구현이 호출되어야 할 지를 알고 있다. 따라서 컴파일러는 최적화를 할 수 있고 가능하다면 코드를 inline으로 변환할 수 있다. 따라서 수행 시간이 매우 빠르게 향상시킬 수 있다.

어떻게 스위프트에서 둘 다 달성(achieve)할 수 있을까?

  • Dynamic Dispatch를 달성하기 위해 상속을 사용한다. 기본 클래스(base class)를 서브 클래싱하고 존재하는 bass class의 메소드를 override한다. 또한 dynamic 키워드를 사용하거나 Objective-c 런타임에 노출될 수 있도록 prefix에 @objc 를 사용한다.
  • Static Dispatch를 달성하기 위해, class와 method가 override되지 않음을 보장하기 위한 키워드로써 finalstatic키워드를 붙인다.

Static Dispatch(or Direct Dispatch)

static dispatch는 컴파일 시간에 명령어들이 어디에 위치하는지 알기 때문에 dynamic dispatch과 비교하여 꽤 빠르다. 그래서 함수가 호출되면, 컴파일러는 직접적으로 작업(operation)을 수행하기 위해 함수의 메모리 주소로 직접적으로 접근(jump)한다. inlining만큼 성능 효과를 볼 수 있고 최적화를 할 수 있다.

Dynamic Dispatch

Dynamic dispatch에서 컴파일 타임 대신에 약간의 오버헤드가 있는 런타임때 구현이 이뤄진다.

왜 dynamic distpatch가 static dispatch보다 성능이 좋지 않음에도 사용하는 이유가 뭘까?

유연성 때문이다. 사실 대부분의 OOP언어들은 다향성이 존재하기 때문에 Dynamic dispatch를 지원한다

  1. Table Dispatch

Dynamic dispatch는 테이블을 사용한다. 특정 메서드 구현을 찾기 위해 감시 테이블(virtual table)이라고 불리우는 함수 포인터 배열 테이블이다.

어떻게 감시 테이블이 작동할까?

  • 모든 subclass는 테이블의 복제본을 소유하고 있다.
  • 테이블은 서브 클래스가 재정의한 모든 메서드는 다른 함수 포인터를 가진다.
  • 서브 클래스가 새로운 메서드를 정의하면, 이 배열의 끝에 메서드 포인터들이 추가된다.
  • 런타임에 컴파일러는 이 테이블을 사용하여 메소드를 호출할 구현을 찾는다.

컴파일러가 table로 부터 구현을 위한 메모리 주소를 읽어야하고 그 이후에 address로 jump하기 때문에 두가지의 추가적인 명령어가 요구된다. 따라서 static dispatch보다 더 느리다.

  1. Message Dispatch

이 dynamic dispatchs는 가장 동적인 기술이다. 사실 이 기술은 꽤 좋아서(최적화는 제외하고) Cocoa Framework는 KVO, CoreData, 다른 것들과 같은 곳들에게서도 많이 사용된다.

이 방법은 Method Swizziling 을 가능하게 하는데, 이 기술은 런타임에 메서드의 기능을 바꾸는 기술이다.

Swift 컴파일러는 이 Dispatch 기술을 성취하기 위해 Objective-C런타임을 사용한다.

명시적으로 우리는 dynamic키워드를 사용한다. swift 4.0이전에 dynamic @objc 를 사용했지만 4.0 이후엔 Objective-C 런타임과 message dispatch에 노출되어지기 위해 우리 메서드 앞에 @objc 키워드를 붙인다.

이것을 위해 Objective-C runtime을 사용하므로 message가 dispatch될 때 런타임은 클래스 계층을 탐색하여 호출할 메서드를 결정한다. 이때 성능은 굉장히 느리다. 성능을 보완하기 위해 캐시를 통해 차이를 만들 수 있다.

컴파일러는 항상 dynamic 키워드를 명시적으로 표시하지 않는다면 dynamic dispatchstatic dispatch로 향상시키려고 항상 시도한다

Example

Value Type

struct Person {
		func isIrritating() -> Bool { } // static
}

extension Person {
    func canBeEasilyPissedOff() -> Bool { } // Static
}

struct와 enum은 value타입이고 상속을 지원하지 않기 때문에, 컴파일러는 value타입은 항상 subclass되지 않음을 알기 때문 항상 static dispatch에 놓여있다.

Protocol

protocol Animal {
    func isCute() -> Bool { } // Table
}

extension Animal {
    func canGetAngry() -> Bool { } // Static
}

Extension 내부에 저장된 메서드는 Static Dispatch를 사용한다.

Class

class Dog: Animal {
    func isCute() -> Bool { } // Table
    @objc dynamic func hoursSleep() -> Int { } // Message
}

extension Dog {
    func canBite() -> Bool { } // Static
    @objc func goWild() { } // Message
}

final class Employee {
    func canCode() -> Bool { } // Static 
}
  • 일반적인 메서드 선언은 Protocol과 같은 원칙을 따른다
  • @objc를 사용하여 objective-c 메서드에 노출되면 메서드는 Message Dispatch를 사용한다
  • 그러나 만약 fianl 키워드를 표시한다면, class는 subclass할 수 없고, static Dispatch를 사용한다

0개의 댓글