본문 바로가기
Programming

Swift 프로토콜

by 나무수피아는 지식의 가지를 뻗어가는 공간입니다. 2025. 8. 1.
반응형

프로토콜 (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 프로토콜은 NamedSpeakable 프로토콜을 상속받아, 두 프로토콜의 모든 요구사항을 포함합니다. 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