
스위프트는 프로토콜 지향 언어(Protocol-Oriented Language)이다.
스위프트는 대부분 구조체로 기본 타입이 구현되어있다. String, Int, Float, Date, URL 등등 익히 사용하고 있는 데이터 타입은 모두 구조체 이며, Array, Set, Dictionary 또한 구조체로 구현되어 있다.
구조체는 클래스와 달리 상속이 불가능하다. 상속도 되지 않는 구조체로 다양한 공통 기능을 가질 수 있는 이유는 Protocol과 Extension에 있다.
프로토콜 이란 특정 기능에 필요한 메소드, 프로퍼티 등을 정의만 해 놓은 것을 말한다. (Java에서 Interface와 유사한 개념이다. 구현은 하지 않고 선언만 된 상태이다.)
이러한 프로토콜은 타입으로 사용이 가능하다.
프로토콜에 대한 자세한 설명은 이 글로 대체한다.
필요한 부분만을 프로토콜로 분리하여 프로그래밍 하는 것을 말한다.
코드로 살펴보자.
Tackable이라는 프로토콜의 정의한다.protocol Talkable {
var topic: String { get set }
func talk(to: Self)
}
Tackable 프로토콜을 채택한 구조체를 정의한다.struct Person: Talkable {
var topic: String
var name: String
func talk(to: Person) {
print("\(topic)에 대해 \(to.name)에게 이야기합니다")
}
}
Person 구조체는 Talkable 프로토콜을 채택하고 있다. 프로토콜을 채택한다는 것은 프로토콜이 가지고 있는 내부 프로퍼티와 메소드를 반드시 구현해야한다는 것을 의미한다. Talkable을 채택한 Person 클래스는 talk(to:) 메소드를 사용할 수 있다.
let elly = Person(topic: "Swift", name: "elly")
let coco = Person(topic: "Java", name: "Coco")
elly.talk(to: coco)
coco.talk(to: elly)
Person의 내부 코드를 알지 못 하더라도, Talkable을 채택하고 있으니 talk(to:) 메소드를 호출 하는 것이 가능하다.
protocol Eatable {
func eat()
}
protocol Moveable {
func run()
func walk()
}
struct Person: Talkable, Eatable, Moveable {
var topic: String
var name: String
func talk(to: Person) {}
func eat(){}
func run(){}
func walk(){}
}
이렇게 필요한 기능은 프로토콜로 묶음으로써 기능의 모듈화가 가능해 진다.
Person의 여러 기능(?)중 특정한 함수만 필요하다면, 특정 프로토콜을 타입으로 사용할 수 있다. 예를들어, eat() 메소드만 필요하다면 Eatable 프로토콜을 타입으로 사용하는 것도 가능하다.
struct Monkey: Talkable {
var topic: String
func talk(to: Monkey) {
print("우끼끼 꺄꺄 \(topic)")
}
}
그런데 Talkable을 채택하는 구조체가 많아 질 수록 매번 talk(to:) 를 구현해야하는 불편함이 존재한다.
이때 사용하는 것이 Extension이다. OOP의 수직적인 확장 구조가 아닌 수평적인 확장구조의 형태를 가진다.
// 익스텐션을 사용한 프로토콜 초기 구현
extension Talkable {
func talk(to: Self) {
print("\(to)! \(topic)")
}
}
struct Person: Talkable {
var topic: String
var name: String
}
struct Monkey: Talkable {
var topic: String
}
let elly = Person(topic: "Swift", name: "elly")
let hana = Person(topic: "Internet", name: "hana")
elly.talk(to: hana)
hana.talk(to: elly)
이렇게 하나의 프로토콜을 만들고, 초기 구현을 해둔다면 여러 타입에서 해당 기능을 사용하고 싶을 때 프로토콜을 채택하기만 하면되는 것이다.
만약, 프로토콜 초기 구현과 다른 동작을 원한다면, 프로토콜의 요구사항을 재정의 해주면 된다.
struct Monkey: Talkable {
var topic: String
func talk(to: Monkey) {
print("\(to)! 우끼기기기끼기기")
}
}