프로토콜 (Protocol)
Swift에서 프로토콜(Protocol)은 특정 속성, 메서드, 이니셜라이저 등을 요구하는 일종의 청사진(blueprint) 역할을 합니다. 클래스, 구조체, 열거형은 이러한 프로토콜을 채택(adopt)하고 요구사항을 구현하여 다양한 기능을 갖춘 객체로 확장될 수 있습니다. 이는 객체 지향 언어의 인터페이스(Interface)와 유사하며, Swift에서는 다형성과 추상화를 구현하는 핵심 도구 중 하나입니다.
1. 프로토콜 선언 및 채택
프로토콜은 protocol 키워드를 사용하여 선언합니다. 프로토콜은 속성과 메서드의 존재를 강제할 수 있지만, 구체적인 구현은 제공하지 않습니다. 클래스(class), 구조체(struct), 열거형(enum)은 모두 프로토콜을 채택하고 해당 요구사항을 구현할 수 있습니다.
protocol Animal {
var name: String { get }
func makeSound()
}
struct Dog: Animal {
var name: String
func makeSound() {
print("Bark")
}
}
let dog = Dog(name: "Buddy")
dog.makeSound() // Bark
Animal 프로토콜은 name 속성과 makeSound() 메서드를 요구합니다. Dog 구조체는 이를 채택하고 해당 요구사항을 충족합니다. 이처럼 프로토콜은 다양한 타입에 동일한 인터페이스를 부여하여 일관성을 유지할 수 있게 도와줍니다.
2. 프로토콜과 다형성
Swift의 프로토콜은 다형성을 구현하는 데 매우 효과적입니다. 서로 다른 타입이라도 동일한 프로토콜을 따르고 있다면, 공통된 방식으로 동작하게 만들 수 있습니다. 이는 함수나 컬렉션에서 유연한 설계를 가능하게 합니다.
struct Cat: Animal {
var name: String
func makeSound() {
print("Meow")
}
}
func makeAnimalSound(animal: Animal) {
print("\(animal.name) says: ", terminator: "")
animal.makeSound()
}
let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")
makeAnimalSound(animal: dog) // Buddy says: Bark
makeAnimalSound(animal: cat) // Whiskers says: Meow
makeAnimalSound() 함수는 Animal 프로토콜을 채택한 어떤 타입이든 받을 수 있습니다. 이는 객체 지향 언어에서 흔히 볼 수 있는 다형성(Polymorphism)의 형태로, 서로 다른 객체들을 하나의 프로토콜 타입으로 다룰 수 있게 해 줍니다. 프로토콜 덕분에 Swift에서는 유연하고 확장 가능한 코드를 작성할 수 있습니다.
3. 프로토콜 확장 (Extension)
Swift의 강력한 기능 중 하나는 바로 프로토콜 확장입니다. 이를 통해 기본 구현을 제공함으로써, 프로토콜을 채택한 타입들이 반드시 모든 메서드를 구현하지 않아도 되도록 만들 수 있습니다. 또한, 이 확장을 통해 공통된 기능을 재사용할 수 있어 코드의 유지보수가 쉬워집니다.
extension Animal {
func sleep() {
print("\(name) is sleeping")
}
}
let dog = Dog(name: "Buddy")
dog.sleep() // Buddy is sleeping
Animal 프로토콜에 sleep() 메서드를 추가함으로써, 이 프로토콜을 채택한 모든 타입은 자동으로 이 메서드를 사용할 수 있습니다. 각 타입별로 중복된 코드를 작성하지 않아도 되는 이점이 있으며, 확장된 메서드는 타입 내부의 구현을 오염시키지 않고 기능을 추가할 수 있어 매우 유용합니다.
4. 프로토콜 상속
프로토콜은 다른 프로토콜을 상속할 수 있습니다. 이를 통해 더 복잡한 요구사항을 조합하고, 계층적으로 프로토콜을 구성할 수 있습니다.
protocol Named {
var name: String { get }
}
protocol Speakable {
func speak()
}
protocol Person: Named, Speakable {}
struct Student: Person {
var name: String
func speak() {
print("Hello, my name is \(name).")
}
}
Person 프로토콜은 Named와 Speakable 프로토콜을 상속받아, 두 프로토콜의 모든 요구사항을 포함합니다. Student 구조체는 Person만 채택하면, 상속된 모든 프로토콜을 동시에 만족해야 합니다. 이는 인터페이스를 조합하여 재사용할 수 있게 하며, 모듈화 된 설계를 가능하게 합니다.
5. 프로토콜과 제네릭
제네릭(Generic)과 프로토콜을 결합하면 더욱 강력하고 재사용 가능한 코드 작성을 할 수 있습니다. 특히 제네릭 함수나 타입에 where절을 이용해 프로토콜을 조건으로 설정할 수 있습니다.
func printAnimalNames<T: Animal>(animals: [T]) {
for animal in animals {
print(animal.name)
}
}
let animals: [Dog] = [Dog(name: "Buddy"), Dog(name: "Max")]
printAnimalNames(animals: animals)
위 예시는 Animal 프로토콜을 따르는 타입 배열을 받아, 각 요소의 name을 출력하는 제네릭 함수입니다. 타입 안정성을 유지하면서도 다양한 타입에 유연하게 적용할 수 있는 코드를 만들 수 있습니다.
결론
Swift의 프로토콜은 객체 간의 공통된 인터페이스를 정의하고, 코드 재사용성과 유연성을 높이는 핵심 기능입니다. 선언, 채택, 확장, 상속, 제네릭과 결합 등 다양한 방식으로 활용할 수 있으며, 추상화와 다형성을 구현하는 데 탁월한 수단입니다. 특히 프로토콜 중심 설계(Protocol-Oriented Programming)는 Swift 언어 철학의 핵심으로, 잘 설계된 프로토콜은 유지보수와 확장성에 매우 유리한 구조를 제공합니다.
'Programming' 카테고리의 다른 글
| Swift 오류 처리 (46) | 2025.08.03 |
|---|---|
| Swift 확장과 접근 제어 (47) | 2025.08.02 |
| Swift 클로저(Closure) (55) | 2025.07.31 |
| Swift 열거형 (106) | 2025.07.30 |
| Swift 옵셔널 (107) | 2025.07.29 |