프로토콜과 다형성
다형성은 하나의 일관된 인터페이스를 통하여 여러 타입과 상호작용을 할 수 있게 해줍니다.
var myPerson: Person
myPerson = SwiftProgrammer(firstName: "Jungsu", lastName: "YEO", birthDate: birthDateProgrammer)
myPerson = FootballPlayer(firstName: "Dan", lastName: "Marino", birthDate: birthDatePlayer)
위 예시에서는 PersonProtocol
타입의 변수를 하나 갖습니다.
다형성은 다양한 타입들을 personProtocol
프로토콜을 따르는 타입의 인스턴스면 변수에 대입할 수 있도록 해줍니다.
연관 타입과 프로토콜
프로토콜을 정의하는 경우 하나 이상의 연관 타입(Associated Type)을 정의하는 것이 유용한 경우가 존재합니다.
연관 타입이란, "우리는 이 프로토콜을 채택하는 타입이 사용할 타입을 정확히 몰라, 그래서 이 프로토콜을 채택하는 타입이 정확한 타입을 정할 거야" 라는 의미입니다.
다음 예시에서는 큐가 구현해야 하는 요구사항을 정의할 Queue
프로토콜입니다.
protocol Queue {
associatedtype QueueType
mutating func enqueue(item: QueueType)
mutating func dequeue() -> QueueType?
func count() -> Int
}
위 프로토콜에서는 QueueType
이라는 연관 타입을 정의하고 enqueue
, dequeue
메소드 내에서 해당 연관 타입을 사용하였습니다.
따라서, Queue
프로토콜을 채택하여 구현하는 모든 타입은 QueueType
플레이스홀더를 위하여 사용할 타입을 명시해야 합니다.
해당 프로토콜을 채택하여 구현한 비제네릭 타입(Int) Queue를 살펴보겠습니다.
struct IntQueue: Queue {
var items = [Int]()
mutating func enqueue(item: Int) {
items.append(item)
}
mutating func dequeue() -> Int? {
if items.count > 0 {
return items.remove(at: 0)
} else {
return nil
}
}
func count() -> Int {
return items.count
}
}
IntQueue
구조체는 Queue
프로토콜을 채택하여 구현한 구현체이며 내부에서 Int 타입을 사용하였습니다.
지금까지 간단히 프로토콜에 대하여 살펴봤으니 이제는 델리게이트 디자인 패턴을 구현하기 위해 프로토콜을 사용하는 방법에 대해 알아보겠습니다.
델리게이션
델리게이션 패턴은 매우 간단하면서 매우 강력하게 동작합니다.
어느 한 타입의 인스턴스(델리게이트 객체)가 다른 인스턴스를 대신하여 동작하는 상황에 잘 맞습니다.
동작을 위임하는 인스턴스는 델리게이트 인스턴스의 참조를 저장하고 있다가 특정 동작이 발생하면 델리게이팅 인스턴스는 계획된 함수 호출을 위해 델리게이트를 호출합니다.
말로 설명하면 어려우니 직접 예제를 보며 이해를 돕도록 하겠습니다.
일단, 델리게이트 객체가 대신해야할 일을 정의할 프로토콜을 작성합니다.
protocol DisplayNameDelegate {
func displayName(name: String)
}
DisplayNameDelegate
프로토콜에서는 델리게이트에서 빤드시 구현해야 하는 메소드를 하나 정의하며, 메소드 이름은 displayName()
입니다.
이번엔 델리게이트를 사용할 구조체를 살펴보겠습니다.
struct Person {
weak var displayNameDelegate: DisplayNameDelegate // 델리게이트 객체
var firstname = "" {
didSet {
displayNameDelegate.displayName(name: getFullname())
}
}
var lastName = "" {
didSet {
displayNameDelegate.displayName(name: getFullName())
}
}
init(displayNameDelegate: DisplayNameDelegate) {
self.displayNameDelegate = displayNameDelegate
}
func getFullName() -> String {
return "\(firstName) \(lastName)"
}
}
Person
구조체는 displayNameDelegate
델리게이트 객체를 가지고 있습니다.
이후 해당 델리게이트 프로토콜 내 메소드 호출이 필요할 경우, 델리게이트의 참조를 통해 메소드를 호출합니다.
이와같이 델리게이트 프로토콜을 이용한 패턴을 델리게이트 패턴 이라고 부릅니다.
프로토콜을 사용한 설계
프로토콜지향 에서는 객체지향의 슈퍼클래스 대신 프로토콜을 사용하며, 이는 요구 사항을 작고 매우 구체적인 프로토콜로 나누기에 적절합니다.
이번 절 예제를 위해 Robots
모델링을 해보겠습니다.
설계하는 로봇은 여러가지 센서를 가지고 서로 다른 옵션을 처리하는 능력이 ㅍㅍ필요합니다.
모든 로봇은 움직임을 갖고 있으므로 이러한 움직임에 대한 요구사항을 정의할 프로토콜을 만드는 것으로 시작해보겠습니다.
protocol RobotMovement {
func forward(speedPercent: Double)
func reverse(speedPercent: Double)
func left(speedPercent: Double)
func right(speedPercent: Double)
func stop()
}
위 프로토콜에서는 이를 따르는 타입은 반드시 구현해야 하는 메소드 다섯 가지를 정의하고 있습니다.
현재는 2차원 좌표계에서의 움직임만 제어하고 있으나 만일 3차원 좌표계에서 움직이는 로봇 또한 추가적으로 개발하게 된다면 아래와 같이 추가 요구 사항을 추가하는 프로토콜을 생성하기 위해 기존 프로토콜을 상속하여 사용할 수 있습니다.
protocol RobotMovementThreeDimension: RobotMovement {
func up(speedPercent: Double)
func down(speedPercent: Double)
}
이 프로토콜을 생성할 떄 원본 RobotMovement
프로토콜로부터 요구 사항을 상속받기 위해 프로토콜 상속을 사용했음을 주목합니다.
이제 로봇 타입을 위한 요구 사항을 정의할 프로토콜을 생성해보겠습니다.
protocol Robot {
var name: String {get set}
var robotMovement: RobotMovementThreeDiemension {get set}
init(name: String, robotMovement: RobotMovementThreeDimension) {
}
이 프로토콜에서는 프로토콜을 따르는 모든 타입이 구현해야만 하는 두 개의 프로퍼티와 한 개의 생성자를 정의하고 있습니다.
이러한 요구사항은 로봇에 필요한 기본 기능을 제공할 것 입니다.
Swift에서는 프로토콜 또한 타입으로 간주합니다.
하지만 프로토콜은 인스턴스를 생성할 수 없으므로 값 타입도 참조 타입도 아닙니다.
'iOS' 카테고리의 다른 글
[iOS] RIBs? (0) | 2021.04.10 |
---|---|
[iOS] [Swift] JSON Parsing with Codable (0) | 2021.02.27 |
[iOS] [Swift] POP - 프로토콜 시작 (0) | 2020.12.26 |
[iOS] [Swift] UIKit Framework (0) | 2020.12.04 |
[iOS] [Swift] RxCocoa 맛보기 (0) | 2020.11.26 |