프로토콜이란 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 말한다.
어떤 프로토콜의 요구사항을 모두 따르는 타입을 '해당 프로토콜을 준수한다'고 표현한다. 프로토콜은 정의하고 제시할 뿐 구현을 하지는 않는다.
protocol 프로토콜 이름 {
프로토콜 정의
}
구조체, 클래스, 열거형 등에서 프로토콜을 채택하려면 타입 이름 뒤에 :을 붙여준 뒤 프로토콜 이름을 쉼표로 구분하여 명시해준다.
Struct SomeStruct: Aprotocol, Bprotocol {
구조체 정의
}
클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 다음에 채택할 프로토콜을 나열해준다.
프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있다. 그러나 프로토콜은 그 프로퍼티의 종류(연산, 저장)는 신경쓰지 않는다. 이름과 타입만 맞도록 구현해주면 된다.
단, 읽고 전용일지 읽고 쓰기 가능일지는 프로토콜이 정해줌.
protocol somprotocol {
var settableproperty: String { get set } //var 키워드로 프로토콜 요구사항(읽고 쓰기) 정의
var notneedtobe: String { get }
}
protocol Anotherprotocol {
static var sometypeproperty: Int { get set } //타입 프로퍼티를 요구하려면 static 키워드 사용
}
특정 인스턴스 메서드나 타입 메서드를 요구할 수도 있다. 요구할 메서드는 프로토콜 정의에서 작성한다. 메서드의 실제 구현부인 {} 부분은 제외하고 이름, 매개변수, 반환 타입 등만 작성하며 가변 매개변수도 허용한다.
protocol A {
func B(data: Any)
}
메서드가 인스턴스 내부의 값을 변경할 필요가 있을 때가 있다. 인스턴스 메서드에서 자신 내부의 값을 변경하고자 할 때 메서드의 func 키워드 앞에 mutating 키워드를 적어 인스턴스 내부의 값을 변경한다고 명시한다.
참조타입인 클래스의 메서드 앞에는 mutating을 명시하지 않아도 인스턴스 내부 값을 바꾸는데 문제가 없지만, 값 타입인 구조체와 열거형의 메서드 앞에서는 mutating 키워드를 붙여야한다.
이니셜라이저를 정의하지만 구현은 하지 않는다. 매개변수를 지정할 뿐, {}안을 구현하지는 않는다.
protocol Named {
var name: String { get }
init(name: String)
}
클래스의 경우에는 요구에 부합하는 이니셜라이저를 구현할 때는 required 식별자를 붙인 요구 이니셜라이저로 구현해야한다. 이 부모클래스를 상속 받은 모든 자식클래스는 해당 프로토콜을 모두 준수해야한다. 그렇기에 반드시 요구 이니셜라이저로 구현해야한다.
class Person: Named { //Named프로토콜 준수
var name: String
required init(name: String) { //요구 이니셜라이저로 구현
self.name = name
}
}
이 클래스를 상속받는 자식클래스는 override, required 모두 표기해야한다. (순서는 무관)
프로토콜은 다중 상속이 가능하다. : 뒤에 상속 받을 프로토콜을 , 로 나열하면 된다.
프로토콜도 타입이기 때문에 타입캐스팅으로 준수하는지 확인, 다운캐스팅 시도, 강제 다운캐스팅을 할 수 있다.
print(KIM is Named) //true (Named는 프로토콜)
if let casted instance: Named = KIM as? Named { //(is랑 다른점을 명확히 모르겠음)
}