스위프트는 프로토콜 지향 언어(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)! 우끼기기기끼기기")
}
}