오늘은 Swift의 핵심 개념인 Protocol, Extension, 그리고 타입 캐스팅에 대해서 하나씩 정리해보려고 한다.
이 세 가지는 Swift에서 꽤 자주 마주치게 되는데, 처음 보면 뭔가 추상적이고, 런타임에 따라 달라지고...
조금 어렵게 느껴질 수 있다. 그래서 오늘은 찬찬히 정리해봤다.
먼저 정의부터 확실하게 짚고 가자.
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
Swift에서는 기존 타입에 기능을 붙일 수 있다.
이게 바로 extension(확장)이다.
extension Int {
func squared() -> Int {
return self * self
}
}
이제 5.squared() => 25가 출력된다.
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는 한 번 이해하면 정말 강력한 문법들이 많은 언어다.
궁금한 게 생기면 또 정리해서 기록해봐야겠다.