Protocols
프로토콜(protocol
) 이란 쉽게 생각해서 인증서라고 보면 된다.
- 프로토콜은 특정 작업이나 기능에 적합한 메서드, 프로퍼티 및 기타 요구사항의 청사진을 정의한다.
Struct
,Class
,Enum
은Protocol
을 채택해서 특정 기능을 실행하기 위해 프로토콜의 요구사항을 실제로 구현할 수 있다.- 프로토콜은 정의를 하고 제시만 할 뿐 스스로 기능을 구현하지는 않는다. 즉, 조건만 정의한다.
- 메서드 구현부인
{ }
중괄호는 사용 불가 하지만, 메서드 이름, 매개변수, 반환 타입 등만 작성 가능하며 매개변수도 사용 가능. - 하나의 타입으로 사용되기 때문에 아래와 같이 타입 사용이 허용되는 모든 곳에 프로토콜을 사용할 수 있다.
- 여러 개의 프로토콜을 채택할 수 있다.
- ⭐️ 프로토콜은 메서드, 프로퍼티 등을 '정의'만 하고, 이 프로토콜을 채택한 곳에서 '구현'을 한다.
protocol MyProtocol {
// 프로토콜 구현부
}
struct MyStruct: MyProtocol {}
class MYClass: MyProtocol {}
클래스처럼 앞에 protocol
키워드를 붙이고, 프로토콜 이름을 정해주면 사용이 가능하다.
그리고 이 프로토콜을 다른 클래스, 구조체, 열거형에서 위처럼 채택해줄 수 있다. 상속받는 것처럼 콜론 : 뒤에 프로토콜 이름을 넣어주면 된다.
그리고 프로토콜은 여러 개를 채택할 수 있다.
struct MyStructure: FirstProtocol, AnotherProtocol {
// struct definition goes here
}
만약 슈퍼클래스를 상속하고 있을 때는 순서가 중요하다. 슈퍼클래스 ➜ 첫 번째 프로토콜 ➜ 또 다른 프로토콜
반드시 슈퍼클래스를 먼저 써주고, 그다음 프로토콜을 채택해줘야 한다.
프로퍼티 요구사항
Swift : 기초문법 [프로퍼티#1 - 저장 프로퍼티]
Swift : 기초문법 [프로퍼티#2 - 연산 프로퍼티]
- 프로토콜은 어떠한
conforming type
(해당 프로토콜을 준수하는 타입)에게 특정 이름과 타입인, 인스턴스 프로퍼티 또는 타입 프로퍼티를 요구할 수 있다. - 프로토콜이 읽기 및 쓰기 프로퍼티를 요구하면, 해당 프로퍼티 요구사항은 상수 저장 프로퍼티 또는 읽기 전용 연산 프로퍼티로 충족되선 안된다.
- 프로토콜에서 읽기만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구 사항을 충족시킬 수 있다.(필요하다면 쓰기만 정의 가능)
- 읽기와 쓰기 프로퍼티는 선언 후
{get set}
을 사용하고, 읽기 프로퍼티는{get}
을 사용한다. - ✔️ 프로토콜은 이 프로토콜이 저장 프로퍼티 인지, 연산 프로퍼티 인지 명시하지 않는다. (프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현해주면 됨)
- ✔️ 프로토콜은 각 프로퍼티에
getter
(읽기)만 가능한지,getter
/setter
(읽기/쓰기) 모두 가능한지 명시해야만 한다.(쓰기만 불가) - ✔️ 프로퍼티 요구사항은 항상 변수 프로퍼티로 선언됨(
var
키워드)
protocol SomeProtocol {
var settableProperty: String { get set }
var notNeedToBeSettableProperty: String { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
static var anotherTypeProperty: Int { get }
}
SomeProtocol
프로토콜을 정의하고, 변수로 두 개의 프로퍼티를 선언했다. settableProperty
은 읽기/쓰기 모두 가능한 프로퍼티, notNeedToBeSettableProperty
은 읽기만 가능하다.
AnotherProtocol
은 타입 프로퍼티인데 반드시 static
키워드를 붙여 정의해야 한다.
클래스의 타입 프로퍼티에는 상속 가능한 타입 프로퍼티인 class
타입 프로퍼티와 상속 불가능한 static
타입 프로퍼티가 있는데, 이 두 타입 프로퍼티를 따로 구분하지 않고 모두 static
키워드를 사용하여 타입 프로퍼티를 요구하면 된다.
다음 예제는 단일 인스턴스 프로퍼티 요구사항이 있는 프로토콜이다.
protocol FullyNamed {
var fullName: String { get }
}
프로토콜에 읽기 전용 String
타입인 변수 fullName
을 정의했다. 즉, 프로토콜에서 fullName
이라는 프로퍼티를 요구했다.
지금 까지 본 것이 바로 정의 부분.
이제 채택 및 준수 부분을 알아보자!
FullyNamed
프로토콜을 구조체에서 채택하려고 아래와 같이 코드를 작성하면 오류가 나는 것을 확인할 수 있다.
protocol FullyNamed {
var fullNamed: String { get }
}
//error: 프로토콜 요구사항을 지키지 않음
struct Person: FullyNamed {
//code
}
✔️ 프로토콜은 이 프로토콜이 저장 프로퍼티 인지, 연산 프로퍼티 인지 명시하지 않는다. (프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현해주면 됨)
요구사항을 다음과 같이 지켜주면 된다.
protocol FullyNamed {
var fullNamed: String { get }
}
struct Person: FullyNamed {
var fullNamed: String
}
✔️ 프로토콜에서 읽기만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구 사항을 충족시킬 수 있다.(필요하다면 쓰기(setter
)만 정의 가능)
이제 이 부분을 알아보자면 '읽기(getter)만 필요로 하는 경우, 연산 프로퍼티든 저장 프로퍼티든 어느 것으로 선언해도 상관없다'라는 의미이다.
상수 저장 프로퍼티
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
let fullName: String
}
var seogun = Person(fullName: "서근 개발노트")
seogun.fullName = "서근"
//error! 'fullName' 은 상수 let 으로 선언되었다.
Person
구조체의 저장 프로퍼티가 let
상수로 정의되어 있기 때문에 최초 한번 "서근 개발노트"를 제외한 다음 값 수정은 불가하다.
변수 저장 프로퍼티
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
var seogun = Person(fullName: "서근 개발노트")
seogun.fullName = "서근"
연산 프로퍼티
연산 프로퍼티는 값을 저장할 수 있고, var
로 선언하며, 반환할 변수가 따로 있어야 한다. 그리고 클래스, 구조체, 열거형에 값을 저장할 저장 프로퍼티가 반드시 하나 있어야 한다.
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var name: String
var fullName: String {
return name
}
}
var seogun = Person(name: "서근 개발노트")
seogun.fullName = "서근"
//프로퍼티에 할당할 수 없음: 'fullName'은(는) 읽기 전용 프로퍼티이다.
연산 프로퍼티를 보면 fullName
에 에러가 났는데, 이유는 fullName
은 읽기 전용 프로퍼티 이기 때문이다. 연산 프로퍼티는 값을 저장하지 않기 때문이다.
이를 해결하기 위해선 쓰기(setter
)를 추가하면 되는데 아까 '만약 필요하다면 쓰기(setter)만 정의 가능' 부분이 그것이다.
하지만 setter
만 가질 순 없기 때문에 get
을 추가해주면 된다.
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var name: String
var fullName: String {
get {
return name
}
set {
self.name = newValue
}
}
}
var seogun = Person(name: "서근 개발노트") //서근 개발노트
seogun.fullName = "서근" //서근
✔️ 프로토콜이 읽기(getter
) 및 쓰기(setter
) 프로퍼티를 요구하면, 해당 프로퍼티 요구사항은 상수(let
) 저장 프로퍼티 또는 읽기 전용 연산 프로퍼티로 충족되선 안된다.
이 문장을 풀어보면 다음과 같다.
protocol FullyNamed {
//fullName은 읽기/쓰기 프로퍼티
var fullName: String { get set }
}
fullName
은 읽기와 쓰기 프로퍼티로 정의되어있는데, 당연히 쓰기(setter
)가 있으니, 값을 변경할 수 없는 상수 저장 프로퍼티는 사용할 수 없다! 또 읽기 전용 연산 프로퍼티도 setter
로 정의되어있는데 '읽기 전용' 이기 때문에 탈락!
protocol FullyNamed {
var fullName: String { get set }
}
struct Person: FullyNamed {
let fullName: String
//오류 : Person 유형이 'FullyNamed' 프로토콜을 준수하지 않는다.
}
var seogun = Person(fullName: "서근")
읽기/쓰기 프로퍼티면 let
상수를 var
변수로 수정해줘야 한다!
그리고 읽기 전용 프로퍼티 부분도 읽기/쓰기 프로퍼티로 정의되어있는데 getter
전용이기 때문에 오류
protocol FullyNamed {
var fullName: String { get set }
}
struct Person: FullyNamed {
var name: String
var fullName: String {
return name
}
//오류 : Person 유형이 'FullyNamed' 프로토콜을 준수하지 않는다.
}
var seogun = Person(name: "서근")
다음과 같이 수정해줘야 한다.
protocol FullyNamed {
var fullName: String { get set }
}
struct Person: FullyNamed {
var name: String
var fullName: String {
get {
return name
}
set {
self.name = newValue
}
}
}
var seogun = Person(name: "서근")
이제 간단한 예시를 들고 이번 게시글은 마무리하려 한다!
Bird
라는 슈퍼클래스가 있고 이 안에는 fly
, layEgg
라는 메서드가 있다고 가정한다.
class Bird {
var isFemale = true
func fly() {
print("새가 날개짓을 하며 날아 오른다.")
}
func layEgg() {
if isFemale {
print("알을 낳을 수 있다.")
} else {
print("수컷은 알을 낳을 수 없다.")
}
}
}
class Eagle: Bird {
func soar() {
print("독수리는 놀이 날아오를 수 있다.")
}
}
let myEagle = Eagle()
myEagle.fly()
myEagle.layEgg()
myEagle.soar()
위처럼 서브클래스를 슈퍼클래스로부터 상속시켜줬고, 서브클래스인 Ealge
에 새로운 soar()
메서드를 정의해서 호출할 수 있었다.
하지만 만약 Airplane
또는 Penguin
처럼 날갯짓을 하여 날아오를 수 없는 경우에는 어떨까?
그럴 때는 override
를 사용하여 fly
메서드를 재정의 할 수 있다.
class Airplane: Bird {
// 슈퍼클래스 Bird의 fly메서드를 재정의 시킬 수 있다.
override func fly() {
print("비행기는 엔진을 사용하여 높이 날아오른다.")
}
}
struct FlyingMuseum {
func flyingDemo(flyingObject: Bird) {
flyingObject.fly()
}
}
let myAirplane = Airplane()
let museum = FlyingMuseum()
museum.flyingDemo(flyingObject: myAirplane)
//비행기는 엔진을 사용하여 높이 날아오른다.
하나 문제점은 Airplane
에는 layEgg
메서드도 사용 가능하다는 것이다. 이럴 때 프로토콜을 사용하면 된다.
Canfly
라는protocol
을 생성하고fly
메서드 넣어준다.Bird
클래스에서fly
메서드를 삭제하고,fly
메서드가 필요한 서브클래스에fly
메서드를 생성한다.Airplane
은 더 이상 슈퍼클래스를 상속받지 않아도 되기 때문에 구조체로 변경 후Canfly
프로토콜을 채택한다.flyingObject
의 데이터 유형을Bird
가 아닌Canfly
로 변경한다.
protocol Canfly {
func fly()
}
class Bird {
var isFemale = true
func layEgg() {
if isFemale {
print("알을 낳을 수 있다.")
} else {
print("수컷은 알을 낳을 수 없다.")
}
}
}
class Eagle: Bird, Canfly {
func fly() {
print("독수리는 날개짓을 하여 하늘 높이 날아오를 수 있다.")
}
func soar() {
print("독수리는 놀이 날아오를 수 있다.")
}
}
//펭귄은 날 수 없기 때문에 Canfly 프로토콜을 채택하지 않는다.
class Penguin: Bird {
func swim() {
print("펭귀은 헤엄을 굉장히 잘한다.")
}
}
// 이제 Airplne은 어떠한 슈퍼클래스도 필요하지 않기 때문에 struct로 변경
struct Airplane: Canfly {
// 슈퍼클래스 Bird의 fly메서드를 재정의 시킬 수 있다.
func fly() {
print("비행기는 엔진을 사용하여 높이 날아오른다.")
}
}
//flyingObject의 유형을 Bird -> Canfly로 변경
struct FlyingMuseum {
func flyingDemo(flyingObject: Canfly) {
flyingObject.fly()
}
}
let myEagle = Eagle()
let myPenguin = Penguin()
let myAirplane = Airplane()
//myAirplane.layEgg 사용 불가
let museum = FlyingMuseum()
museum.flyingDemo(flyingObject: myAirplane) //비행기는 엔진을 사용하여 높이 날아오른다.
museum.flyingDemo(flyingObject: myEagle) //독수리는 날개짓을 하여 하늘 높이 날아오를 수 있다.
//museum.flyingDemo(flyingObject: myPenguin) 사용 불가
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 기초문법 [프로토콜#3 - 타입으로서의 프로토콜, Delegate] (0) | 2022.01.28 |
---|---|
Swift : 기초문법 [프로토콜#2 - 메서드 요구사항] (0) | 2022.01.27 |
Swift : 기초문법 [타입 캐스팅(Type Casting)] (1) | 2022.01.24 |
Swift : 기초문법 [상속#3 - 클래스의 이니셜라이저 convenience, required] (0) | 2022.01.24 |
Swift : 기초문법 [상속#2 재정의 override] (0) | 2022.01.23 |