TIL: 타입으로써의 프로토콜

Royce·2025년 3월 29일

Swift 문법

목록 보기
49/63

타입으로써의 프로토콜(Protocols as Type)

  • Swift에서 프로토콜은 타입(형식)으로 사용할 수 있다
  • 이는 프로토콜이 일급 객체(First Class Citizen) 이기 때문이다
  • 즉, 프로토콜은 클래스, 구조체, 열거형과 동일하게 타입으로 취급될 수 있다

일급 객체(First Class Citizen)

  • 프로그래밍 언엉에서 일급 객체란 다음과 같은 조건을 모두 만족하는 객체를 말한다
  1. 변수나 네이터 구조에 담을 수 있다
  2. 함수의 인자(파라미터)로 전달할 수 있다
  3. 함수의 반환값으로 사용할 수 있다
  4. 할당된 객체는 언제든지 생성, 수정, 삭제가 가능하다
  • Swift에서 프로토콜은 일급 객체이므로, 이러한 모든 특성을 만족한다
  • 예를 들어, 프로토콜 타입을 변수로 선언하거나 함수의 파라미터로 전달하는 것이 가능하다

프로토콜 선언 및 구현

  • Appliance라는 프로토콜을 선언하고, 두 가지 타입(WashingMachine 클래스, AirConditioner 구조체)에 구현하는 예제
protocol Appliance {
    func start()
    func stop()
}

class WashingMachine: Appliance {
    func start() {
        print("세탁기 시작")
    }
    
    func stop() {
        print("세탁기 정지")
    }
}

struct AirConditioner: Appliance {
    func start() {
        print("에어컨 켜기")
    }
    
    func stop() {
        print("에어컨 끄기")
    }
    
    func setTemperature(to temperature: Int) {
        print("\(temperature)도로 설정")
    }
}

프로토콜 타입 사용의 장점

프로토콜 타입 배열 사용하기

  • 여러 객체를 동일한 프로토콜 타입으로 묶어서 관리할 수 있다
let devices: [Appliance] = [WashingMachine(), AirConditioner()]

for device in devices {
    device.start()  // start() 함수는 프로토콜의 요구 사항이므로 호출 가능
}
  • 위의 코드에서 devices 배열은 Appliance 타입으로 선언되었기 때문에, 배열에 담긴 객체들은 모두 Appliance 프로토콜의 요구 사항(start(), stop())만 사용 가능하다

프로토콜 타입으로 저장 시의 주의점

  • 프로토콜 타입으로 저장하면 프로토콜에서 선언된 메서드나 속성만 접근 가능하다
  • 즉, 객체가 실제로 추가 구현한 메서드나 속성에는 접근할 수 없다
  • 프로토콜 타입으로 저장할 때는 그 객체가 어떤 타입인지 모르기 때문에, 프로토콜에 정의된 기능만 사용할 수 있다
let aircon: Appliance = AirConditioner()  // 프로토콜 타입으로 저장
aircon.start()       // 호출 가능 (Appliance 프로토콜에 정의됨)
aircon.stop()        // 호출 가능 (Appliance 프로토콜에 정의됨)
// aircon.setTemperature(to: 25)  // 에러 발생! - Appliance 프로토콜에 정의되지 않았기 때문
  • 위의 예제에서 airconAppliance 타입으로 저장되었기 때문에, 실제로는 AirConditioner 객체일지라도 Appliance 프로토콜에 선언된 메서드만 호출할 수 있다
  • 만약 setTemperature() 메서드를 호출하려면 타입을 변환(다운캐스팅)해야 한다

프로토콜 준수성 검사 (Type Checking)

  • Swift에서는 isas 연산자를 사용하여 객체가 특정 프로토콜을 준수하는지 확인하거나, 타입 변환을 수행할 수 있다

is 연산자 (타입 확인)

  • 특정 인스턴스가 프로토콜을 준수하는지 확인할 수 있다
  • 반대로, 프로토콜 타입이 더 구체적인 타입인지 확인할 수도 있다
let aircon = AirConditioner()

print(aircon is Appliance)         // true (AirConditioner는 Appliance를 준수한다)
print(devices[1] is AirConditioner) // true (배열의 두 번째 요소가 AirConditioner 타입이다)

as 연산자 (타입 변환)

  • as 키워드는 업캐스팅(Upcasting) 과 다운캐스팅(Downcasting) 에 사용된다

업캐스팅 (as 연산자)

  • 객체를 프로토콜 타입으로 변환한다
let device: Appliance = aircon as Appliance  // AirConditioner를 Appliance 타입으로 업캐스팅
device.start()
device.stop()

다운캐스팅 (as? / as! 연산자)

  • 프로토콜 타입으로 저장된 인스턴스를 실제 타입으로 변환한다
  • as? : 옵셔널로 변환 (타입이 맞지 않으면 nil 반환)
  • as! : 강제 변환 (타입이 맞지 않으면 런타임 에러 발생)
if let airConditioner = devices[1] as? AirConditioner {  
    airConditioner.setTemperature(to: 20)   // AirConditioner 타입의 메서드를 사용할 수 있다
}

요약

  • Swift에서 프로토콜은 일급 객체 이므로 변수나 배열에 저장할 수 있고, 함수의 인자로 전달되거나 반환값으로 사용될 수 있다
  • 또한, is 연산자를 사용하여 타입 확인이 가능하며, as 연산자를 사용하여 타입 변환을 안전하게 수행할 수 있다
  • 중요하게도, 프로토콜 타입으로 저장할 경우에는 해당 프로토콜에 정의된 메서드나 속성만 호출할 수 있다
  • 이를 통해 프로토콜을 사용하면 객체의 타입을 명확히 하지 않아도 공통된 기능을 쉽게 사용할 수 있게 해주며, 코드의 유연성과 확장성 을 크게 높일 수 있다
profile
iOS 개발자 지망생

0개의 댓글