🔥 프로토콜 조합
때로는 하나의 타입이 여러 프로토콜을 동시에 준수하도록 요구하는 것이 유용할 수 있습니다. 이런 경우 프로토콜 조합을 사용하여 여러 프로토콜을 하나의 요구사항으로 결합할 수 있습니다. 프로토콜 조합은 조합에 포함된 모든 프로토콜의 요구사항을 결합한 임시 로컬 프로토콜을 정의한 것처럼 동작합니다. 프로토콜 조합 자체는 새로운 프로토콜 타입을 정의하지 않습니다.
프로토콜 조합은 SomeProtocol & AnotherProtocol과 같은 형태를 가집니다. 필요한 만큼의 프로토콜을 나열할 수 있으며, 앰퍼샌드(&)로 구분합니다. 프로토콜 목록 외에도 프로토콜 조합에는 하나의 클래스 타입을 포함할 수 있는데, 이를 통해 필수 상위 클래스를 지정할 수 있습니다.
다음은 Named와 Aged라는 두 개의 프로토콜을 함수 매개변수의 단일 프로토콜 조합 요구사항으로 결합하는 예제입니다:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// "Happy birthday, Malcolm, you're 21!"이 출력됩니다.protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// "Happy birthday, Malcolm, you're 21!"이 출력됩니다.이 예제에서 Named 프로토콜은 name이라는 가져올 수 있는 String 속성에 대한 단일 요구사항을 가집니다. Aged 프로토콜은 age라는 가져올 수 있는 Int 속성에 대한 단일 요구사항을 가집니다. 두 프로토콜 모두 Person이라는 구조체에서 채택되었습니다.
또한 이 예제에서는 wishHappyBirthday(to:) 함수를 정의합니다. celebrator 매개변수의 타입은 Named & Aged로, 이는 "Named와 Aged 프로토콜을 모두 준수하는 모든 타입"을 의미합니다. 함수에 전달되는 구체적인 타입은 중요하지 않으며, 필요한 두 프로토콜을 모두 준수하기만 하면 됩니다.
그 다음, 예제에서는 birthdayPerson이라는 새로운 Person 인스턴스를 생성하고 이를 wishHappyBirthday(to:) 함수에 전달합니다. Person은 두 프로토콜을 모두 준수하므로 이 호출은 유효하며, wishHappyBirthday(to:) 함수는 생일 축하 메시지를 출력할 수 있습니다.
다음은 이전 예제의 Named 프로토콜과 Location 클래스를 결합하는 예제입니다:
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// "Hello, Seattle!"이 출력됩니다.class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// "Hello, Seattle!"이 출력됩니다.beginConcert(in:) 함수는 Location & Named 타입의 매개변수를 받습니다. 이는 "Location의 하위 클래스이면서 Named 프로토콜을 준수하는 모든 타입"을 의미합니다. 이 경우 City는 두 요구사항을 모두 만족합니다.
birthdayPerson을 beginConcert(in:) 함수에 전달하는 것은 유효하지 않은데, 그 이유는 Person이 Location의 하위 클래스가 아니기 때문입니다. 마찬가지로, Location의 하위 클래스를 만들었지만 Named 프로토콜을 준수하지 않는다면 해당 타입의 인스턴스로 beginConcert(in:)을 호출하는 것도 유효하지 않습니다.
프로토콜 조합을 사용하면 여러 프로토콜의 요구사항을 결합하여 더욱 구체적이고 제한적인 타입 요구사항을 표현할 수 있습니다. 이를 통해 코드의 안전성과 명확성을 높일 수 있죠.
다음은 프로토콜 조합의 활용 예시를 보여주는 코드입니다:
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
struct Duck: Flyable, Swimmable {
func fly() {
print("오리가 날아갑니다!")
}
func swim() {
print("오리가 수영합니다!")
}
}
func performActions(on creature: Flyable & Swimmable) {
creature.fly()
creature.swim()
}
let donald = Duck()
performActions(on: donald)
// "오리가 날아갑니다!"와 "오리가 수영합니다!"가 출력됩니다.protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
struct Duck: Flyable, Swimmable {
func fly() {
print("오리가 날아갑니다!")
}
func swim() {
print("오리가 수영합니다!")
}
}
func performActions(on creature: Flyable & Swimmable) {
creature.fly()
creature.swim()
}
let donald = Duck()
performActions(on: donald)
// "오리가 날아갑니다!"와 "오리가 수영합니다!"가 출력됩니다.이 예제에서는 Flyable과 Swimmable이라는 두 개의 프로토콜을 정의하고, Duck 구조체에서 이들을 채택합니다. performActions(on:) 함수는 Flyable & Swimmable 타입의 매개변수를 받아 해당 객체의 fly()와 swim() 메서드를 호출합니다.
donald라는 Duck 인스턴스를 생성하고 performActions(on:) 함수에 전달하면, 오리가 날아가고 수영하는 동작이 수행됩니다.
이처럼 프로토콜 조합을 활용하면 특정 기능들의 조합을 요구하는 함수나 메서드를 유연하게 정의할 수 있습니다. 이는 코드의 재사용성을 높이고 타입 안정성을 보장하는 데 도움이 됩니다.
프로토콜 조합은 Swift의 강력한 기능 중 하나로, 프로토콜 지향 프로그래밍(Protocol-Oriented Programming)의 핵심 개념입니다. 프로토콜 조합을 적절히 활용하면 코드의 모듈화와 유연성을 크게 향상시킬 수 있습니다.
Swift에서 프로토콜은 매우 중요한 역할을 하며, 프로토콜 조합은 이를 더욱 확장하여 다양한 요구사항을 조합할 수 있게 해줍니다. 프로토콜 조합을 사용하면 클래스 상속 계층과는 별개로 타입에 대한 제약 조건을 명시할 수 있어 코드의 유연성과 재사용성이 높아집니다.
앞으로도 Swift 프로그래밍에서 프로토콜과 프로토콜 조합을 적극 활용하여 더욱 간결하고 표현력 있는 코드를 작성해 보시기 바랍니다!










