Swift

[Swift] 프로토콜

pearhyunjin 2024. 8. 28. 12:58

 

Swift에서 프로토콜(Protocols)은 객체 지향 프로그래밍의 기본이 되는 중요한 개념으로, 프로토콜을 사용하면 클래스, 구조체, 열거형이 특정 역할을 수행하기 위해 따라야 하는 메서드, 속성, 기타 요구 사항을 정의할 수 있다. 

 

프로토콜(Protocol)

프로토콜은 특정 역할을 수행하기 위해 필요한 메서드와 속성의 청사진을 정의하는 것을 말한다. 프로토콜 자체는 실제 구현을 포함하지 않으며, 단지 특정 기능을 제공하기 위해 어떤 메서드와 속성이 필요한지를 명시한다.

프로토콜의 기본 문법

프로토콜은 protocol 키워드를 사용하여 정의되며 아래와 같은 예시처럼 사용 가능하다.

protocol Describable {
    var description: String { get }
    func describe() -> String
}
 

위 코드에서 Describable 프로토콜은 두 가지 요구 사항을 정의한다.

  • description: 읽기 가능한 String 타입의 속성
  • describe(): String을 반환하는 메서드

 

프로토콜 채택과 구현

프로토콜을 채택하면 해당 프로토콜이 요구하는 모든 속성과 메서드를 구현해야 한다. 프로토콜을 채택하려면 클래스, 구조체, 열거형 선언 뒤에 콜론(:)을 붙이고, 프로토콜 이름을 명시하면 된다.

예제: Person 구조체에 프로토콜 채택하기

struct Person: Describable {
    var name: String
    var age: Int
    
    // Describable 프로토콜의 요구사항을 구현합니다.
    var description: String {
        return "\(name), \(age) years old"
    }
    
    func describe() -> String {
        return "This is \(name), who is \(age) years old."
    }
}
 

Person 구조체는 Describable 프로토콜을 채택하고 있으며, 이로 인해 description 속성과 describe() 메서드를 구현해야 한다. Person 구조체는 이제 Describable 프로토콜을 따르는 객체로 간주될 수 있다.

 

 

프로토콜의 활용

프로토콜은 다양한 상황에서 활용될 수 있다. 특히 함수의 매개변수 타입으로 사용하면, 해당 프로토콜을 채택한 어떤 객체든 받을 수 있게 되어 코드의 유연성을 높여주고, 서로 다른 타입 간의 일관된 인터페이스를 제공한다.

예제: 프로토콜을 사용하는 함수

struct Car: Describable {
    var model: String
    var year: Int
    
    var description: String {
        return "\(model), \(year) model"
    }
    
    func describe() -> String {
        return "This is a \(model) from \(year)."
    }
}

func printDescription(of describable: Describable) {
    print(describable.describe())
}

let john = Person(name: "John", age: 30)
let myCar = Car(model: "Tesla", year: 2022)

printDescription(of: john) // 출력: This is John, who is 30 years old.
printDescription(of: myCar) // 출력: This is a Tesla from 2022.
 

위의 코드에서 printDescription(of:) 함수는 Describable 프로토콜을 따르는 어떤 객체든 받을 수 있다. 이를 통해 Person이나 Car와 같은 서로 다른 타입을 동일한 함수로 처리할 수 있다.

 

 

프로토콜 상속

Swift의 프로토콜은 다른 프로토콜을 상속받아 더 많은 요구 사항을 추가할 수 있습니다. 이를 통해 기존 프로토콜을 확장하고, 더 복잡한 인터페이스를 정의할 수 있다.

예제: 프로토콜 상속

protocol Identifiable {
    var id: String { get }
}

protocol DetailedDescribable: Describable, Identifiable {
    func detailedDescription() -> String
}
 

DetailedDescribable 프로토콜은 Describable과 Identifiable을 상속받아, 추가로 detailedDescription() 메서드를 요구한다. 이를 통해 하나의 프로토콜이 여러 기능을 결합할 수 있다.

 

 

프로토콜 확장 (Protocol Extensions)

Swift에서는 프로토콜에 기본 구현을 제공할 수 있는 프로토콜 확장 기능이 있다. 이를 통해 프로토콜을 채택한 모든 타입에 공통된 메서드를 제공할 수 있으며, 특정 메서드에 대한 기본 구현을 정의할 수 있다.

예제: 프로토콜 확장

extension Describable {
    func describe() -> String {
        return "No additional information."
    }
}
 

위의 코드는 Describable 프로토콜을 채택한 타입이 describe() 메서드를 구현하지 않더라도, 기본적으로 "No additional information."이라는 문자열을 반환한다. 물론, 각 타입이 이 메서드를 재정의하여 자신만의 동작을 구현할 수도 있다.

 

 

프로토콜과 다형성 (Polymorphism)

프로토콜은 Swift에서 다형성을 구현하는 중요한 도구로 다형성은 서로 다른 타입이 동일한 메서드나 속성을 통해 동일한 동작을 할 수 있도록 하는 개념이다. 프로토콜을 사용하면, 클래스뿐만 아니라 구조체와 열거형도 다형성을 활용할 수 있다.

예제: 다형성 구현

let describableItems: [Describable] = [john, myCar]

for item in describableItems {
    print(item.describe())
}
 

위의 예제에서 describableItems 배열은 Describable 프로토콜을 따르는 여러 객체를 담고 있고, 이 배열의 각 항목은 Describable 프로토콜에 정의된 describe() 메서드를 통해 다형적으로 처리된다.

 

 

 


 

프로토콜은 해당 프로토콜을 준수하는 타입(Conforming type)에게 특정 이름과 타입인 인스턴스 프로퍼티 또는 타입 프로퍼티를 요구할 수 있다. 프로토콜은 이 프로퍼티가 저장/연산 프로퍼티 중 무엇인지를 명시하지 않으며 오직 프로퍼티의 이름과 타입만 요구된다.

프로토콜은 각 프로퍼티에 gettable한지 gettable/settable인지를 명시해야 한다.

 

프로퍼티에 대해서는 다음에 다시 자세히 정리해보기로 하고, get/set에 대해서만 간단하게 살펴보려고 합니다.

 

gettable, settable

프로토콜에서 속성을 정의할 때 get과 set 키워드를 사용하여 속성의 접근자를 지정할 수 있다.

  • { get } : 읽기만 가능한 속성을 요구. 프로토콜을 채택하는 타입은 해당 속성을 읽을 수만 있으면 된다.
  • { get set } : 읽기와 쓰기가 가능한 속성을 요구. 프로토콜을 채택하는 타입은 해당 속성을 읽고 쓸 수 있어야 한다.

예제: gettable 속성과 settable 속성

- Vehicle 프로토콜 정의

protocol Vehicle {
    var currentSpeed: Double { get set }
    var description: String { get }
}
 

위의 Vehicle 프로토콜은 두 가지 속성을 요구한다.

  • currentSpeed: get과 set 모두 가능한 속성으로, 차량의 현재 속도를 나타냅니다.
  • description: get만 가능한 읽기 전용 속성으로, 차량의 설명을 제공합니다.

- Car 구조체에 프로토콜 채택

struct Car: Vehicle {
    var currentSpeed: Double = 0.0
    
    var description: String {
        return "The car is moving at \(currentSpeed) km/h."
    }
}
  • currentSpeed 속성 : get과 set이 모두 가능하므로, 프로토콜의 요구 사항을 충족한다.
  • description 속성 : get만 가능하며, 읽기 전용 속성으로 사용된다.

- 사용

var myCar = Car()
myCar.currentSpeed = 120.0
print(myCar.description) // 출력: The car is moving at 120.0 km/h.
 

이 예제에서 myCar는 Car 구조체의 인스턴스로, Vehicle 프로토콜의 요구 사항을 모두 만족한다. currentSpeed는 get 및 set이 가능하므로 속도의 읽기와 쓰기가 모두 가능하다.

 

get과 set의 유연성

프로토콜에서 { get set }으로 정의된 속성은 get과 set이 모두 가능한 속성을 구현해야 하지만, 이를 채택하는 타입에서는 좀 더 유연하게 구현할 수 있다.

  • 저장 속성(Stored Property) : 직접 값을 저장하고, get 및 set을 사용하여 값을 읽고 쓸 수 있다.
  • 연산 속성(Computed Property) : 값을 계산하여 반환하는 방식으로 구현할 수 있다. 이 경우에도 get과 set을 통해 값을 읽고, 특정 로직에 따라 값을 설정할 수 있다.

>> 관련해서 Property를 추가로 정리해볼 예정

 

set 요구사항이 없는 get 속성

프로토콜에서 { get }만을 요구하는 속성은 반드시 읽기만 가능해야 하며, 프로토콜을 채택한 타입에서는 이를 저장 속성, 연산 속성, 또는 상수(let)로 구현할 수 있다. 이 속성은 내부적으로는 set할 수 있어도 외부에선 오직 읽기만 가능하도록 제한할 수 있다.

 

 


Swift의 프로토콜은 코드의 유연성을 높이고, 객체 간의 일관된 인터페이스를 제공하는 데 중요한 역할을 한다. 프로토콜을 통해 클래스, 구조체, 열거형이 특정 역할을 수행하기 위해 필요한 규칙을 정의하고, 다양한 객체 간의 상호 작용을 원활하게 할 수 있으며 프로토콜을 적절히 활용하면 유지보수성이 높고, 재사용 가능한 코드를 작성하는 데 큰 도움이 된다.

 

 

 

* https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/

* https://zeddios.tistory.com/255