TIL: 프로토콜의 확장(Protocol Extension)

Royce·2025년 3월 29일

Swift 문법

목록 보기
52/63

프로토콜의 확장(Protocol Extension)

  • Swift의 프로토콜은 확장을 통해 기본 구현을 제공할 수 있다
  • 이 방식은 여러 타입에서 동일하게 구현해야 하는 기능을 프로토콜 확장으로 제공하여 코드 중복을 방지하고, 유지보수를 용이하게 만든다
  • 또한, Virtual Table과 Witness Table, 구조체의 메모리 저장 위치(Stack 또는 Heap) 에 대한 이해가 중요하다

프로토콜 정의 및 구현의 문제점

  • 프로토콜을 정의하고 이를 여러 타입에서 구현할 때, 중복 코드가 발생할 수 있다

프로토콜 정의하기

protocol Device {    // 기기를 켜고 끄는 기능을 요구하는 프로토콜
    func powerOn()   
    func powerOff()  
}
  • Device 프로토콜은 powerOn()powerOff() 메서드의 구현을 요구한다

기존 방식의 구현 (반복적인 구현 문제)

class Laptop: Device {   
    func powerOn() {
        print("노트북 전원 켜기")
    }
    
    func powerOff() {
        print("노트북 전원 끄기")
    }
}

struct Tablet: Device {    
    func powerOn() {
        print("태블릿 전원 켜기")
    }
    
    func powerOff() {
        print("태블릿 전원 끄기")
    }
}
  • Laptop 클래스와 Tablet 구조체 모두 같은 기능을 구현해야 하는 상황
  • 코드가 중복되므로 유지보수가 어려워질 수 있다

프로토콜 확장을 통한 기본 구현 제공

  • Swift의 프로토콜 확장(Extension) 을 사용하면 기본 구현을 제공할 수 있다

프로토콜 확장 사용하기

extension Device {                         
    func powerOn() { print("기기 전원 켜기") }   
    func powerOff() { print("기기 전원 끄기") }  
    func reset() { print("기기 설정 초기화") }   
}
  • Device 프로토콜의 기본 구현을 제공하여 중복 구현을 방지할 수 있다
  • reset() 메서드는 요구 사항이 아닌 추가 기능으로 제공된다

프로토콜 확장을 통한 다형성 제공 (프로토콜 지향 프로그래밍)

  • 프로토콜 확장을 이용하여 다형성을 구현할 수 있다

클래스에서 프로토콜을 채택하고 구현하기

class Smartphone: Device {   
    func powerOn() { print("스마트폰 전원 켜기") }  // 기본 구현을 덮어씀
    
    func reset() { print("스마트폰 설정 초기화") }  // 추가 메서드를 재정의
}
  • Smartphone 클래스는 powerOn()을 재정의하고 reset()도 재정의한다
  • 기본 구현을 제공하지만, 필요시 덮어쓸 수 있다

구조체에서 프로토콜을 채택하고 구현하기

struct SmartWatch: Device {   
    func powerOn() { print("스마트워치 전원 켜기") }  // 기본 구현을 덮어씀
    
    func reset() { print("스마트워치 설정 초기화") }  // 추가 메서드를 재정의
}
  • SmartWatch 구조체는 powerOn()을 재정의하고 reset()도 재정의한다
  • 기본 구현과 확장을 모두 사용할 수 있다

Virtual Table과 Witness Table의 차이점

  • Swift에서 클래스와 구조체는 프로토콜을 다르게 처리한다

Virtual Table (클래스의 동적 디스패치)

  • 클래스는 가상 테이블 (Virtual Table, V-Table) 을 사용한다
  • 메서드 호출 시 런타임에 메서드를 동적으로 찾는다

Witness Table (구조체의 정적 디스패치)

  • 구조체는 목격자 테이블 (Witness Table, W-Table) 을 사용한다
  • 메서드 호출 시 컴파일 타임에 함수 포인터가 결정된다 (정적 디스패치)

프로토콜 확장의 Direct Dispatch 원리

  • Swift에서 프로토콜 확장에서 구현된 메서드 (reset()) 는 Virtual Table 또는 Witness Table 에 포함되지 않는다
  • 대신 직접 메서드 주소를 삽입하여 호출하는 방식 (Direct Dispatch) 을 사용한다

코드 예제 (클래스 타입)

let phone: Device = Smartphone()
phone.powerOn()       // 출력: 스마트폰 전원 켜기 (Virtual Table 이용)
phone.powerOff()      // 출력: 기기 전원 끄기 (Virtual Table 이용)
phone.reset()         // 출력: 스마트폰 설정 초기화 (Direct Dispatch 방식)

코드 예제 (구조체 타입)

var watch: Device = SmartWatch()
watch.powerOn()       // 출력: 스마트워치 전원 켜기 (Witness Table 이용)
watch.powerOff()      // 출력: 기기 전원 끄기 (Witness Table 이용)
watch.reset()         // 출력: 스마트워치 설정 초기화 (Direct Dispatch 방식)

구조체의 메모리 저장 위치 (Stack 또는 Heap)

  • Swift에서 구조체의 메모리 저장 위치는 크기와 사용 방식, 그리고 프로토콜 타입으로 선언되었는지 여부에 따라 다르다

구조체의 저장 위치 기준

자료형크기 (Byte)최대 개수 (스택 저장)힙 저장 여부 (개수 초과 시)
Int83힙으로 이동
Double83힙으로 이동
Float46힙으로 이동
Bool124힙으로 이동
Character161힙으로 이동
String최소 24항상 힙힙에 저장
Array최소 24항상 힙힙에 저장
Dictionary최소 24항상 힙힙에 저장
  • 작은 구조체 (24바이트 이하): 스택에 저장
  • 큰 구조체 (32바이트 이상): 힙에 저장되고, 참조 포인터는 스택에 저장
  • 프로토콜 타입으로 선언된 구조체: 무조건 힙에 저장 (타입 소거 발생)

요약

  • 프로토콜 확장은 기본 구현을 제공하여 코드 중복을 방지할 수 있다
  • Virtual Table (클래스)와 Witness Table (구조체)의 차이를 이해하는 것이 중요하다
  • 클래스는 동적 디스패치 방식, 구조체는 정적 디스패치 방식을 사용한다
  • 프로토콜 확장에서 제공된 메서드는 Direct Dispatch 방식으로 호출된다 (빠르고 효율적)
  • 구조체의 저장 위치는 크기와 프로토콜 사용 여부에 따라 달라진다
profile
iOS 개발자 지망생

0개의 댓글