프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의한다.
구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)
해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있다.
프로토콜 사용자의 입장에서, 이 타입은 이 프로토콜을 준수하고 있기 때문에 이런 기능을 다 수행할 수 있겠구나~ 하는 것을 명확히 한다.
protocol Talkable {
// 프로퍼티 요구
var topic: String { get set }
var language: String { get }
// 메서드 요구
func talk()
// 이니셜라이저 요구
init(topic: String, language: String)
}
get
은 읽기만 가능해도 상관 없다는 뜻이며, get
과 set
을 모두 명시하면 읽기와 쓰기 모두 가능한 프로퍼티라는 뜻이다.변수 language는 프로토콜 정의에 의해 읽기 전용이므로 읽기만 가능한 let으로 구현해줘도 되지만, var도 읽기가 가능하므로 var로 구현해줘도 된다.
struct Person: Talkable { // Person 구조체는 Talkable 프로토콜을 채택했다
// 프로퍼티 요구 준수
var topic: String
let language: String
// 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체 가능
// var language: String { return "한국어" }
// 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체 가능
// var subject: String = ""
// var topic: String {
// set {
// self.subject = newValue
// }
// get {
// return self.subject
// }
// }
// 메서드 요구 준수
func talk() {
print("\(topic)에 대해 \(language)로 말합니다")
}
// 이니셜라이저 요구 준수
init(topic: String, language: String) {
self.topic = topic
self.language = language
}
}
프로토콜은 클래스와 상속 문법이 유사하지만 클래스와 다르게 다중상속이 가능하다.
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
아래의 SomeType 구조체처럼 ReadWriteSpeakable
은 Readable
과 Writable
을 상속받기 때문에 이 프로토콜을 채택한 구조체는 read(), write(), speak()를 모두 구현해야 한다. 하나라도 구현을 생략할 경우 오류가 발생한다.
struct SomeType: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
클래스에서 상속과 프로토콜 채택을 동시에 하려면 상속받으려는 클래스를 먼저 명시하고 그 뒤에 채택할 프로토콜 목록을 작성한다.
class SuperClass: Readable {
func read() { }
}
class SubClass: SuperClass, Writeable, ReadSpeakable {
func write() { }
func speak() { }
}
is
연산자를 사용해 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있다.
let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()
var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false
someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true
또는 딕셔너리에서 값을 꺼냈거나 Any타입으로 값을 받아왔거나 한다면 이런 식으로 as
연산자를 활용해 타입캐스팅해서 사용할 수 있다.
someAny = sup
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
} // read
if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
someReadSpeakable.speak()
} // 동작하지 않음
someAny = sub
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
} // read
야곰의 스위프트 기본 문법 강좌를 수강하며 작성한 내용입니다.