프로토콜 & 익스텐션

BS_Lee·2025년 6월 30일

swift

목록 보기
11/21

오늘은 Swift의 핵심 개념인 Protocol, Extension, 그리고 타입 캐스팅에 대해서 하나씩 정리해보려고 한다.
이 세 가지는 Swift에서 꽤 자주 마주치게 되는데, 처음 보면 뭔가 추상적이고, 런타임에 따라 달라지고...
조금 어렵게 느껴질 수 있다. 그래서 오늘은 찬찬히 정리해봤다.


프로토콜(Protocol)이란?

먼저 정의부터 확실하게 짚고 가자.

Protocol은 어떤 속성이나 메서드가 필요하다는 ‘규약’을 정리해놓은 것이다.
쉽게 말하면 "이걸 따르는 타입은 이 기능을 반드시 구현해야 해!" 라는 일종의 계약서 같은 느낌이다.
자바로 치면 인터페이스 역할을 하는 친구라고 보면된다.

protocol Greetable {
    var name: String { get }
    func greet()
}

이걸 보면, name이라는 속성이 있어야 하고, greet()라는 메서드도 구현해야 한다.


프로토콜로 인스턴스를 만들 수 있을까?

let someone = Greetable() // 응 안됨.

에러가 난다.
왜냐하면 프로토콜은 구현체가 아니라 설계도이기 때문이다.
직접 쓸 수 없고, 반드시 해당 프로토콜을 "채택한" 타입(예: struct, class)을 통해서 사용해야 한다.

struct Human: Greetable {
    var name: String
    func greet() {
        print("Hello, \(name)")
    }
}

let tom = Human(name: "Tom")
tom.greet() // Hello, Tom

extension?

Swift에서는 기존 타입에 기능을 붙일 수 있다.
이게 바로 extension(확장)이다.

extension Int {
    func squared() -> Int {
        return self * self
    }
}

이제 5.squared() => 25가 출력된다.


프로토콜에도 extension을 쓸 수 있다.

protocol CanJump {
    func jump()
}
extension CanJump {
    func jump() {
        print("Jumping...")
    }
}

만약 어떤 타입이 CanJump만 채택하고 jump()를 직접 구현하지 않으면?
extension에 있는 기본 구현이 자동으로 사용된다.

struct Cat: CanJump {}
Cat().jump() // Jumping...

프로토콜 속성에 { get }, { get set }이 붙는 이유는?

이건 처음 보면 문법이 헷갈릴 수 있다.

protocol HasName {
    var name: String { get }
    var age: Int { get set }
}
  • { get }: 읽기만 가능 (let이나 var 모두 OK)
  • { get set }: 읽기 + 쓰기 (→ 반드시 var만 가능)

예를 들어서 이렇게 구현할 수 있다.

struct Person: HasName {
    let name: String   // 읽기 전용
    var age: Int       // 읽고 쓸 수 있음
}

자바에서 생각하면 getter와 setter를 생각하면 쉬울듯하다...


그럼 private(set)과 뭐가 다를까?

또 헷갈릴 수 있는 게 private(set)이다.

struct User {
    private(set) var name: String
}

이건 외부에서는 읽기만 가능하지만 내부에서는 수정 가능하다.

문법위치의미
{ get set }프로토콜외부에서도 읽고 쓸 수 있어야 함
private(set)구조체 정의외부는 읽기만 가능, 내부는 읽고 쓸 수 있음

결국 목적은 비슷하지만, 프로토콜은 ‘요구 조건’, 구조체는 ‘접근 제어’ 관점인 거다.


Any, is, as?는 언제 써야 할까?

여기서 타입 캐스팅 개념으로 넘어가 보자.

func describe(obj: Any) {
    if obj is Vehicle {
        print("obj conforms to Vehicle")
    } else {
        print("nope")
    }
}
  • Any: 모든 타입을 받을 수 있다. (Int, String, Struct, Class 기타 등등...., 자바에서 Object클래스와 비슷한 개념)
  • is: 런타임에 타입이 맞는지 체크 (java에서 instance of 와 비슷한 개념)

그럼 as?는 뭘까?

if let vehicle = obj as? Vehicle {
    vehicle.increaseSpeed(by: 10)
}

여기서 as?조건부 타입 캐스팅이다.

연산자설명실패 시
as?조건부 캐스팅nil 반환
as!강제 캐스팅 (실패 시 앱 죽음)런타임 에러
as확정 타입으로 캐스팅컴파일 시 타입 체크됨

🧠 그런데 구조체에서 as? 썼더니 왜 값이 안 바뀔까?

struct Car: Vehicle {
    var speed = 0
    mutating func increaseSpeed(by amount: Int) {
        speed += amount
    }
}

let car = Car()
increaseSpeedIfVehicle(obj: car)

print(car.speed) // 0

헷갈릴 수 있다. 분명 increaseSpeed 했는데 왜 0이지?

→ Swift의 struct값 타입이다.
즉, obj as? Vehicle에서 복사본이 넘어가기 때문에,
원래 car는 그대로인 것이다.

  • 해결하려면 class를 쓰거나
  • inout 매개변수로 직접 전달해야 한다.

정리

개념한 줄 요약
protocol타입이 따라야 할 규약이다
extension기존 타입이나 프로토콜에 기능을 추가한다
{ get set }읽기와 쓰기 둘 다 가능해야 한다는 의미
private(set)외부는 읽기만 가능, 내부는 수정 가능
Any모든 타입을 받을 수 있는 타입
is타입 체크 연산자
as?조건부 캐스팅 (옵셔널로 반환)
값 타입 캐스팅 문제복사본을 바꾸는 거라 원본은 변하지 않음

Swift는 한 번 이해하면 정말 강력한 문법들이 많은 언어다.
궁금한 게 생기면 또 정리해서 기록해봐야겠다.

0개의 댓글