스위프트에서 정의하는 프로토콜
프로토콜 (Protocols)
준수하는 타입이 구현해야 하는 요구사항을 정의합니다.
프로토콜 (protocol) 은 메서드, 프로퍼티, 그리고 특정 작업이나 기능의 부분이 적합한 다른 요구사항의 청사진을 정의합니다. 프로토콜은 요구사항의 구현을 제공하기 위해 클래스, 구조체, 또는 열거형에 의해 채택될 수 있습니다. 프로토콜의 요구사항에 충족하는 모든 타입은 프로토콜에 준수 (conform) 한다고 합니다.
준수하는 타입의 요구사항을 지정하는 것 외에도 요구사항의 일부를 구현 하거나 준수하는 타입에 추가 기능을 구현하기 위해 프로토콜을 확장할 수 있습니다.
머라는 걸까.... 항상 어렵게 말하는 우리의 스위프트 공식홈페이지
그래서 이것 저것 찾아보게 되었다.
Protocol의 정의를 찾아보면 뭐 협약, 통신 규약 등이 있지만
좀 더 쉽게 우린 약속이라고 생각을 해보는 것임!!
자, 프로토콜을 이해하기 위해서 우린 밴드라는 것을 생각해볼 것임
내가 음악을 개짱 좋아해서 밴드라는 것을 만들고 싶음!!!
그렇다면, 밴드를 만들기 위해선 어떤 요소들이 필요할까?!!
물론 뭐 베이스도 있을 수 있고, 악기야 추가로 좀 더 많을 수 있겠지만
보통의 밴드라고 함은 위와 같이 기타, 드럼, 피아노, 보컬이 필수적으로 있어야 되잖음??
근데 밴드라는 것은 저 사람들이 모여서 수다만 떨고 끝나는 게 아니라,
어떠한 곡을 연주한다는 것이 목표잖음!?!??!
그렇다면 밴드에 대한 또다른 요구사항 중 하나는 바로, 연주가 될 것임!
그래그래ㅋ 적어도 밴드를 만들기 위해선 위와 같은 약속은 지켜줘야지 ㅋ
하고 우리가 위처럼 어떤 Band라는 것에 대한 가이드를 만들어버렸음ㅎㅎ
이것이 사실 프로토콜의 전부임ㅎㅎ
자, 이젠 위 내용들을 좀 더 프로그래밍적으로 생각해보는 것임!
Band를 구성할 때 필요한 속성 vocal, guitar, piano, drum은 프로퍼티로,
play라는 연주에 대한 행위는 메서드로 만들 수 있잖음!?!
따라서, 프로토콜이란 것은,
실제 보컬이 누구고, 기타가 누구고, 어떤 곡을 연주 할 것이고를
실제로 지정(구현)하는 것이 아니라
Band라는 것은,
이런 이런 프로퍼티는 꼭 필요해요!
이러이러한 메서드도 꼭 필요해요!
하고 해당 기능에 필요한 요구 사항을 선언해두는 것이 바로 Protocol 이란 것임!!
앞으로 내가 어떤 밴드를 만들 때,
마치 [밴드를 만들 때 필요한 101가지 방법]과도 같은
미리 정의된 Band라는 프로토콜을 통해서(채택해서) 만들 경우,
보컬, 기타, 피아노, 드럼이라는 속성은 필수적으로 필요하며,
연주하다 라는 기능도 필수적이겠구나!!
를 생각해본다면,
여러분은 프로토콜을 모두 이해한 것입니다 ㅋ
이해가 안 가셔도 괜찮음 설명을 개떡같이 해서..ㅎㅎ;;
코드를 보면서 차근차근 이해해 봅시당 :)
프로토콜은 특정 작업에 적합한 메서드, 프로퍼티 등을 정의해놓은 설계도면이고,
이는 클래스, 구조체, 열거형에 채택되어 구현되어집니다.
프로토콜을 구현하는 일은 마치 건축하기 전, 건축물의 설계도면을 구상하는 것 같은 역할을 합니다.
1-1. 프로토콜을 정의하는 방법
protocol Band {
var drum: String { get set }
var vocal: String { get set }
var piano: String { get set }
var guitar: String { get set }
func play() }
|
우리가 위에서 Band라는 것으로 만든 프로토콜(약속)은 위처럼 정의할 수 있음!!
프로토콜이란 것은 앞서 누누(간다!)히 말했듯이
프로퍼티를 선언하여 값을 직접 정의하고, 메서드를 직접 구현 하는 것이 아님!!
이 프로토콜을 따르려면 이러 이러한 것들이 필요하다!! 라는 약속을 정의해두는 것임
밴드라는 프로토콜을 따르기 위해선
드럼 보컬 피아노 기타 프로퍼티가 반드시 정의되어 있어야 하고,
play란 메서드가 반드시 정의되어 있어야 해요!!
라는 것을 선언해두는 것이 바로 Band란 프로토콜임!!
프로퍼티는 let으로 선언하면 안 되나요?
{ get set }이 뭔가요? get만 두면 안되나요?
메서드는 어떤 식으로 선언해 두는 건가요!?
프로토콜이 gettable과 settable 인 프로퍼티를 요구할 경우 프로퍼티 요구사항은 저장된 프로퍼티 상수 또는 읽기전용 계산된 프로퍼티는 충족할 수 없습니다. 프로토콜이 gettable 인 프로퍼티 만 요구할 경우 이 요구사항은 모든 종류의 프로퍼티에 충족될 수 있고 이것이 유용한 경우 settable 또한 프로퍼티에 대해 유효합니다.
프로퍼티 요구사항은 항상 var 키워드와 함께 변수 프로퍼티로 선언됩니다. gettable과 settable 프로퍼티는 타입 선언 뒤에 { get set } 으로 작성하여 나타내고 gettable 프로퍼티는 { get } 으로 작성하여 나타냅니다.
일단 스위프트가 시키는대로 하는게 제일 중요하다.
겟터 셋터 사용하는 방법
protocol Student {
var height: Double { get set }
var name: String { get }
static var schoolNumber: Int { get set }
}
- 해당 프로토콜은 학생의 키, 이름, 학번을 정의하였고, 이를 체택하는 타입은 해당 프로퍼티를 구현해줘야한다.
struct Aiden: Person {
var heartRate = 100
static func breathing() {
print("숨을 쉽니다")
}
func sleeping(time: Int) -> Bool {
if time >= 23 {
return true
} else {
return false
}
}
mutating func running() {
heartRate += 20
}
}
print(Aiden.breathing())
// 숨을 쉽니다.
var aiden = Aiden()
print(aiden.sleeping(time: 23))
// true
print(aiden.heartRate)
// 100
aiden.running()
print(aiden.heartRate)
// 120
- 프로퍼티는 저장 프로퍼티나 연산 프로퍼티 둘다 사용해서 구현할 수 있다.
1-2. 프로토콜을 채택하는 방법
자 우리는 Band라는 프로토콜을 선언함으로써,
Band라는 일종의 약속을 만들어둔 것임
그럼 이제 그것을 클래스, 구조체, 열거형이 채택하여 사용할 수 있게끔 만들 수 있는데,
자, 우리가 만약 ABand라는 새로운 밴드를 만들 것임!!
struct ABand {}
|
위처럼!!
근데, 이 ABand라는 밴드는
밴드를 생성할 때 필요한 요소들을 미리 정의해둔 Band라는 프로토콜(약속)을 따르고 싶어!!
라고 할 경우에,
struct ABand: Band {}
|
위처럼, 마치 상속을 받을 때처럼,
Band라는 프로토콜을 클래스 이름 옆에 선언해주는 것만으로 해당 프로토콜을 "채택"하게 되는 것임!!
그러면 이제, ABand라는 구조체에서 에러가 뜸
야 너! Band라는 프로토콜 따른다매!!
너어 Band라는 프로토콜을 따르려면
보컬, 기타, 피아노, 드럼 이란 프로퍼티가 정의되어 있어야 하구!!
play란 메서드도 구현되어 있어야 한다구!!
왜 약속 안지켜!!
하고 에러가 뜨는 것임!
class ABand: Band {
var drum: String = "A"
var vocal: String = "B"
var piano: String = "C"
var guitar: String = "D"
func play() {
print("day6 예뻤어 연주 중!")
}
}
|
따라서 위와 같이 Band 프로토콜 안에 선언만 되어 있던 녀석들을
채택한 곳인 ABand라는 구조체 안에서 실제로 구현 해주는 것이 필요함!!
이렇게 하면 이제 에러가 사라짐!
이것이 바로 프로토콜임!!
프로퍼티/메서드에 대한 껍데기만 제공하되, 실제 구현은 채택한 곳에서 하는 것임!!
어느 정도 이해가 갔을 거라 믿구,
그럼 처음에 봤던 애플 정의를 다시 본다면
프로토콜이란, 어떤 기능에 적합한
특정 메서드, 프로퍼티 및 기타 요구 사항의 청사진(Bluprint)을 의미합니다
프로토콜은 클래스, 구조체, 열거형에 의해 채택되며,
프로토콜에 정의된 요구사항의 실제 구현을 제공합니다
프로토콜의 요구 사항을 모두 충족하는 모든 유형(클래스/구조체/열거형)은
해당 프로토콜에 부합하다고 합니다.
이젠ㅇ ㅣ해 할 수 있기를..!!! :)
2. 필수라기엔.. 베이스는 구하기 힘든데...
위에서 아무렇지 않게 프로토콜 안에 선언되어 있는 프로퍼티 / 메서드는 모드
required 즉, 필수적인 것임
따라서 채택하는 곳에서 구현하지 않을 경우 에러가 남!!
근데,, 베이스 같이 구하기 힘든 멤버도 프로토콜 안에 선언해두고 싶은데,,
이녀석은 밴드마다 있을수도,, 없을 수도 있거든요,,
희귀한 녀석이거등요,,
할 경우에,
optional로 선언할 수도 있음!!
@objc protocol Band {
var drum: String { get set }
var vocal: String { get set }
var piano: String { get set }
var guitar: String { get set }
@objc optional var bass: String { get set }
func play() }
|
bass란 프로퍼티 앞에 @objc optional을 붙일 경우(프로토콜에도 당연히 @objc 선언),
이땐 bass라는 프로퍼티에 한해서 required가 아닌 optional로 바뀜!
채택해주는 곳에서 꼭 선언해주지 않아도 에러가 안 남
뭐죠 bass 선언은 자유라며요!! 왜 에러가 나죠!!
이때의 에러는, bass란 프로퍼티를 선언하지 않아서 나는 에러가 아니라,
Band라는 프로토콜을 채택하는 녀석이 "구조체"이기 때문에 나는 에러임
왜냐?
@objc라는 문법을 붙이는 순간 Objective-C에서도 사용될 수 있단 것인데,
Objective-C에서 프로토콜은 오로지 "클래스 전용"에서만 채택할 수 있음..
따라서 호환성을 위해 @objc가 붙는 순간 자동으로,
@objc protocol Band: AnyObject {
var drum: String { get set }
var vocal: String { get set }
var piano: String { get set }
var guitar: String { get set }
@objc optional var bass: String { get set }
func play() }
|
이렇게 클래스 전용일 때 사용하는 AnyObject가 자동으로 채택된다고 보면 됨!!!
Optional인 만큼, 채택하는 곳에서 선언 했을지 안 했을지 여부를 알 수 없기 때문에,
만약 Band라는 프로토콜 타입을 통해 bass에 접근할 경우,
이때 bass는 String? 즉, Optional(String) 타입으로 접근해야 함!
프로토콜(Protocol)이란?
그리고 프로토콜은 일급 객체이며, 기본적으로 규약, 협약이라는 의미를 가진다.
일급객체란? (쉽게 말해 변수나 상수처럼 사용 가능하다는 의미)
- 변수에 할당 가능
- 함수의 파라미터로 전달 가능
- 반환값으로 사용 가능
규약과 협약은 '~을 해야한다' 또는 '~에 대한 정의'가 되어있고,
실제 실행은 해당 규약과 협약을 맺은 공동체(행위의 주체)가 한다.
프로토콜도 동일하게, 단순히 무엇을 해야하는지, 무엇을 정의해야하는지에 대한 요구사항만을 담고, (청사진의 의미)
구현은 프로토콜을 채택한 클래스, 구조체, 열거형에서 한다.
'뭔 소리야?' 싶을 수 있는데, 예제 코드를 보며 이해해보자.
protocol Music {
// 프로토콜은 초기화하지 않음
func playPiano(title: String, time: Int)
func sing(title: String)
var name: String { get }
}
예제를 보면, 프로토콜은 초기화하지 않은 변수와 실행구문이 없는 함수를 선언하고, 상수 let을 사용하지 않는다.
왜냐하면, 프로토콜은 메서드나 변수의 이름과 타입만 정하기 때문에,
프로토콜 내 변수는 Computed Property(계산 속성)만 가능하며,
Computed Property는 실행할 때 초기화하기 때문에, 값이 변경된다라는 의미로 해석되기 때문이다.
만약 let으로 선언하게 된다면, 친절하게도 상수가 아닌 변수로 선언하라고 강제해준다.

요약하면, 프로토콜은 메서드나 속성 등에 대한 요구사항(청사진)을 정의하는 일급객체라고 할 수 있겠다.
그래서 이런 알맹이없는 녀석을 왜 쓰는데? 라는 생각을 할 수도 있겠다.
바로 프로토콜을 설명해도 좋겠지만, 왜 필요하게 되었는지를 이해하면 더 쉽게 이해할 수 있지 않을까?
왜 Swift는 프로토콜 사용을 지향하는가? ( = 프로토콜이 왜 필요했을까? )
Swift는 클래스의 단점과 구조체에서의 사용을 위해 프로토콜을 지향하게 되었다.
클래스의 문제점
1. 클래스의 단일 상속 원칙(Swift 기준)
- Swift의 클래스는 일부 다른 언어와는 달리 단일 상속만 가능하며, 당연히 다중 상속은 불가하다.
- 따라서 여러 클래스의 속성과 메서드를 상속받기 위해서는 클래스 상속 계층이 쌓거나, 구조를 잘 설계해야 한다.

- 여러 클래스의 속성을 가져오기 위해서는 아래와 같은 구조를 형성해야 한다.


- 이와 같은 구조의 문제는, 상위 클래스의 구조를 그대로 따라야하기에 불필요한 속성과 메서드를 가져올 수 있다는 것이다.
2. 구조체에서 사용 불가
- Swift는 클래스와 구조체를 모두 사용하는데, 구조체는 상속이 불가하다.
- 상속은 메모리 주소를 참조하는 Reference(참조) 타입만 가능한데 구조체는 Value(값) 타입 이기 때문이다.

프로토콜의 사용
- 다중 상속 가능
- 프로토콜도 프로토콜에게 상속이 가능하다
- 클래스(참조타입), 구조체(값 타입)에서 모두 채택 가능
- 프로토콜의 요구사항(무엇을 하고, 무엇을 정의하는지)은 프로토콜을 채택한 클래스나 구조체 등에서 구현(어떻게 할 것인지)한다.


프로토콜의 확장
- 사실 이 부분이 재밌고 흥미로웠는데, 프로토콜도 요구사항을 직접 구현할 수 있다.
- 흥미로웠던 이유는 프로토콜의 상속과 더불어 사용하면 Coordinator 패턴 활용 시 편리한 구현이 가능할 것 같다는 생각이 들었기 때문이다.

- 먼저 class와 struct를 구분하지 않고 생각해보자.
- extension으로 구현한 TestProtocol1을 채택한 두 인스턴스(testClass, testStruct)가 실행될 때,
- testClass는 클래스 내 구현이 되어있지 않고, testStruct는 구현을 한 것으로 비교해봤다.
- testClass: extension으로 기본 구현된 "회사갈거야?"가 출력
- testStruct: 구조체 내부에 직접 구현한 "출근" 이 실행됨.
- 위의 예시에서는 class와 struct를 바꿔서 실행해도 동일한 결과를 보여주지만, 사실 완전히 같은 것은 아니다.


- 위 Class와 Struct 예시 모두 TestProtocol을 채택하고 있는데, 출력이 다르다.
- 인스턴스(testClass2, testStruct2)의 타입에 따른 출력을 비교했을 때,
Protocol 타입으로 업캐스팅(UpCasting)했을 때 차이가 발생함을 알 수 있다. - 즉, 프로토콜 확장자에 대해 Class는 사용자 구현이 우선이고, Struct는 타입에 따른 구현이 적용된다는 말이다.
- 그 이유는 Class가 Reference타입, Struct가 Value타입이라는 것에 있는데, 이 부분에 대해서는 따로 글을 작성하도록 하겠다.
프로토콜은 Swift 전반에 걸쳐 두루 사용되는 문법 중 하나로, 아주아주 중요하고 자주 쓰이기 때문에, 제대로 이해할수록 좋다. 사실 지금의 정리도 상당부분 축약된 면이 있는데, 실제 사용함에 있어서는 이정도로도 충분하다고 생각한다.
특히 이전 포스팅에서 다룬 Delegate패턴과 Coordinator패턴 모두 프로토콜을 활용한 방식이기 때문에 디자인패턴을 다룰 때에도 알아야하는 문법이니 꼭 알고 넘어가야한다.