[Swift] Method Dispatch 2편 - 프로토콜

어흥·2024년 6월 18일

Swift

목록 보기
25/28

프로토콜에서 Method Dispatch은 어떻게 이루어질까?

https://developer.apple.com/videos/play/wwdc2016/416/?time=1474

이 영상이 공식적인 저의 첫 WWDC가 되겠습니다. (다른 WWDC 본거 같은데 기억이 안나서 공식적으로 첫 영상 ㅎ) 암튼 Protocol에서 dispatch 방식은 24분 35초 부터 시청

이 영상에 대한 자세한 포스팅은 해당 포스팅을 참고하시면 되겠습니다.

WWDC2014 - protocol

저번 method dispatch 포스팅에서 value type 인스턴스는 direct(static) dispatch 방식을 따르고 reference type 인스턴스는 table(dynamic) dispatch를 알게 되었다.

그렇다면 프로토콜 타입을 따르는 인스턴스는 어떨까? 알아보자!

프로토콜은 Initial Declaration 경우와 Extension 경우 다른 방식으로 이루어진다.

Initial Declaration

아래와 같이 프로토콜 Drawable이 있고 해당 프로토콜을 채택한 구조체 Point, Line이 있다.

protocol Drawable { func draw() }

struct Point : Drawable {
	var x, y: Double func draw() { ... }struct Line : Drawable {
	var x1, y1, x2, y2: Double func draw() { ... }

drawables은 프로토콜 Drawable 를 채택한 타입을 담는 배열이다.

⁉️ 이때, drawables 배열의 요소 타입은 프로토콜의 타입인데 반복문 속 draw를 각각 어떻게 실행될까?

var drawables: [Drawable]

for d in drawables { 
	d.draw()
}

당연히 프로토콜을 채택한 타입마다 다르게 구현하고 있으니 static이 아닌 dynamic 방식을 사용해야할 것이다!

이처럼 프로토콜 본체에서 정의된 메서드에 대해서는 Table(Dynamic) Dispatch 방식으로 이루어진다.

하지만 클래스와 같은 방식으로 Virtual Table를 이용하는 거는 아니고 Witness Table 사용한다. (이에 대해서 이용한 방식으로 더 자세한 건 해당 포스트를 참고하길 바란다. )

다른 table을 사용하는 건 protocol은 위 그림과 같이 상속관계를 통한 계층 구조를 갖지 않으므로 공통 인스턴스가 존재하지 않기 때문이다.

그래서 프로토콜의 요구사항에 대한 테이블을 하나 갖는다. 이게 바로 Protocol Witness Table인 것이다.

Extension

프로토콜의 extension 기능에 대해서 간단히 집고 넘어가자.

protocol Drawable{
    func draw()
}
extension Drawable{
    //요구사항의 기본 구현 제공
    func draw() {print("draw something")}
    
    //필수 요구사항은 아님
    func paint(){print("paint something")}
}
  1. 본체에서 정의한 필수 요구사항이 아닌 다른 메서드를 추가로 구현할 수 있다.
  2. 프로토콜 extension에서 본체에서 작성한 요구사항의 기본 구현을 제공할 수 있다. → 기본 구현이 제공된 메서드에 대해서는 프로토콜을 채택한 타입에서 필수로 구현하지 않아도 된다.

경우 1. 프로토콜 타입 인스턴스가 필수 요구사항이 아닌 확장에서 구현한 메서드(paint())를 호출할 때

경우 2. 프로토콜 요구사항에 정의된 메서드가 확장에서 기본 구현되어 있는 경우

위 2가지 경우에 대해서 method dispatch가 일어날까?

경우 1. 요구사항 외 메서드 구현 Method Dispatch in Extension

extension Drawable{
    //요구사항의 기본 구현 제공
    func draw() {print("draw something")}
    
    //필수 요구사항은 아님
    func paint(){print("paint something")}
}

struct Point : Drawable {
    func paint(){
        print("paint Point")
    }
}

struct Line : Drawable {
    func paint(){print("paint Line")}
}

let drawables: [Drawable] = [Line(), Point() ]
for drawable in drawables {
    drawable.draw()
}

실행 결과

drawables 의 배열 타입이 Drawable 이므로 당연히 실행할 코드는 정해져 있다. → Extension에서 구현한 paint 함수를 실행할 것이다.

이처럼 언제나 프로토콜의 기본 메서드를 실행하기 때문에 Static Dispatch로 동작한다.

경우 2. 요구사항 메서드 기본 구현 Method Dispatch in Extension

protocol Drawable{
    func draw()
}
extension Drawable{
    //요구사항의 기본 구현 제공
    func draw() {print("draw something")}
    
    //필수 요구사항은 아님
    func paint(){print("paint something")}
}

struct Point : Drawable {
    func draw(){
        print("draw Point")
    }
}

struct Line : Drawable {
    func draw(){print("draw Line")}
}

let drawables: [Drawable] = [Line(), Point() ]
for drawable in drawables {
    drawable.draw()
}

실행 결과

draw() 메서드는 Drawable 프로토콜의 필수 요구사항에 해당되는 메서드로 Line 인스턴스가 생성될 때, Protocol Witness Table도 생성되는데 이때 Protocol Witness Table은 각각 메서드의 주소를 저장한다.

따라서 위 그림처럼 사전에 저장된 draw 함수를 찾아가는 것이다.

이처럼 2번째 경우에는 Table(Dynamic) dispatch 방식을 갖는다.

그러면 Method Dispatch에 대해서 지금까지 공부한 내용을 정리한 표는 다음과 같이 될 것이다.

Reference.

Method Dispatch in Swift
Method Dispatch in Swift, and its effect on performance
wwdc2016_Understanding Swift Performance
Swift의 Dispatch 규칙
앨런 Swift문법 마스터 스쿨

0개의 댓글