You are on page 1of 14

SECTION 6

프로토콜지향 프로그래밍

2015년 6월, 애플의 월드 와이드 디벨로퍼 컨퍼런스 기간에 가장 유명해진 프리젠테이션


은 데이브 아브라함의 "프로토콜 지향 프로그래밍"이었습니다.
아브라함은 스스로 지칭하기를 "최초의 프로토콜 지향 프로그래밍 언어"라는 것이 될 수 있
도록 스위프트를 처음부터 - 클래스를 대체할 수 있는 경량 구조체와 열거형, 그리고 프로
토콜을 가진 - 설계했다고 밝혔습니다. 이런 배경 설명에서 참석한 개발자들의 폭넓은 박
수 갈채를 받았습니다. 프로토콜은 스위프트 언어 설계의 핵심 사항이라고 아브라함은 설
명했습니다.
애플측 사람들 입장에서는 스위프트의 가장 중요한 항목으로 프로토콜 지향 프로그래밍이
크게 다뤄지기를 기대하고 있는 것 같습니다.
스위프트2 에는 "프로토콜 익스텐션" 기능이 포함되었습니다. 프로토콜 익스텐션은 클래스
와 구조체와 열거형에서 처럼 프로토콜의 익스텐션에서 메서드와 프로퍼티 구현이 가능하
도록 합니다. 클래스, 구조체, 열거형은 하나 이상의 프로토콜을 따를 수 있으므로 메서드
와 프로퍼티를 복수의 프로토콜로부터 얻을 수 있게 되고, 스위프트에서 하나의 수퍼 클래
스만을 허용하는 클래스 기반 상속보다 프로토콜 지향 프로그램밍이 더 강력하다는 공감대
를 가져오게 되었습니다.
이 장에서는 먼저 클래스와 상속의 문제점을 짚어보고 프로토콜을 통해 어떻게 개선하는지
살펴보겠습니다. 프로토콜 익스텐션을 적용할 수 있는 다양한 방법을 알아보고 프로토콜에
적용되는 소위 말하는 self 요구사항(self requirement)에 대해서 설명하겠습니다. 마지막에는 프
로토콜 지향 프로그래밍의 함축된 의미에 대해서 살펴보겠습니다.

1.클래스 상속의 문제점


지금처럼 객체지향 프로그래밍이 실전에서 폭 넓은 영역에 적용되어오는 과정에서 객체지
향 프로그래밍이야 말로 소프트웨어 기술이 반드시 따라야하는 접근 방식이라고 굳게 믿어
지게 되었습니다. 하지만 현실은 많은 명망있는 사람들로부터 비판을 받아 온 것도 사실입
니다. 이런 객체지향 프로그래밍에 대한 비판의 대부분은 클래스의 상속에 관련된 것이었
습니다.

182
앨런 케이(Alan Kay)가 이런 말을 했다고 합니다. "내가 객체지향 프로그래밍을 고안해 냈
을 때, C++을 염두에 둔것은 아니었다.", "Java는 MS-DOS 이후로 컴퓨팅에 닥친 가장 큰
재앙이다."
네델란드 학회와 튜링상 수상자인 에드거 딕즈트라(Edsgar Dijkstra)는 객체지향 프로그래
밍에 대해서 매우 비판적이었다고 합니다.
(다음의 인용문은 지어낸 말이 확실함에도 불구하고 자주 인용되어 회자되어 왔습니다. "객
체지향 프로그래밍은 캘리포니아에서 생겨난 것 중에서 예외적으로 나쁜 아이디어이다.")
물론 프로그래머들이 "내가 사용하는 이 언어는 대단하고 네가 사용하는 그 언어는 쓰레기
야"라고 주장하는 사례를 찾아 보는 것이 어려운 것은 아닙니다. 더 많은 관심이 가고 믿을
만한 설명은 아마도 객체지향 프로그래밍을 실제로 하고 있는 (Gang of Four라 불리는 디
자인 패턴(Design Patterns)의 저자들과 같은) 사람들로 부터 나온 것일 겁니다. 이들을 포
함해서 다수의 사람들이 상속의 오남용에 대해서 경고하고 있습니다. 이들이 제시하는 원
칙들 중의 하나가 "favor composition over inheritance"입니다. 의역하면 정말로 상속이
필요한 경우가 아니라면 상속 대신에 컴포지션 패턴을 사용하라는 것입니다. 컴포지션은
상속 관계 없이 클래스들을 혼합하여(composition) 사용하는 것을 말합니다. 따라서 어떤 클래
스가 다른 클래스에 정의되어 있는 메서드를 사용해야할 때, 그 클래스의 인스턴스를 생성
하여 (또는 대상 클래스의 메서드를 직접 호출하거나) 메서드를 실행하여 나온 결과값을 이
용하라는 것입니다. 클래스의 프로퍼티를 다른 클래스의 인스턴스로 정의하는 것은 이러한
활용에서 대상 클래스의 메서드를 쉽고 빠르게 활용할 수 있도록 합니다. 상속과 콤포지션
모두 (유지보수의 악몽을 낳는) 메서드가 불필요하게 중복되는 것을 피하기 위해서 입니다.
도대체 상속의 어떤 점이 잘못되었다는 것일까요? 일반적으로 세 가지 비판이 주를 이룹니
다.
• 상속은 현실의 사물을 표현하기에 별로 좋은 방법이 아닙니다. 시간이 지남에 따라
유연성이 떨어집니다.
• 일상적으로 구현되는 상속은 안전하지 않거나 비효율적인 데이터 공유를 자동적으
로 내포하게 됩니다.
• 상속은 서로 다른 객체간의 강한 커플링(tight coupling)을 피해야한다는 패러다임에 너무
강압적입니다.

Not a good, or stable, representation


처음에는 객체와 객체들간의 관계가 현실 세계를 반영하는 좋은 모델이라고 생각했기 때문
에 객체지향 시스템에서 클래스와 상속의 개념이 훌륭한 아이디어라고 받아들여 졌습니다.

183
그리고 클래스와 상속은 실제 세상을 표현하는 좋은 방법이라고 여겨졌습니다. 왜냐하면
범주와 분류의 시스템(생물학에서의 분류학과 도서관에서의 책 분류 방법)에서의 확실한
성공 사례를 보여 주었기 때문입니다.
당연히 생물학 시스템도 예외가 있습니다. 새이지만 날지 못하는 펭귄이나. 포유류이지만
날수 있는 박쥐, 포유류이지만 바다에서 살고 있는 고래. 이런 예외 경우가 성가시긴해도,
소프트웨어 클래스의 상속과 오버라이딩을 통해서 소프트웨어적으로 처리할 수 있다고 생
각하였습니다.
소프트웨어의 발전은 천천히 진행되는 반면 변화는 요구사항은 매우 빠르게 변화하고 있습
니다. 상속에 관련된 문제들 중의 한 가지는 상속 자체의 유연성이 떨어진다는 것입니다.
(크레이 셔키(Clay Shirky)의 논문 “Ontology is Overrated”에서 인용한) 종교에 관한
Dewey Decimal 카테고리 시스템(도서관에서 책을 분야별 번호룰 분류하는 시스템)을 살
펴보면 카테고리 변경이 일어났을 때 미치는 영향에대해 잘 알 수 있습니다.

200: Religion

210 Natural theology


220 Bible
230 Christian theology
240 Christian moral & devotional theology
250 Christian orders & local church
260 Christian social theology
270 Christian church history
280 Christian sects & denominations
290 Other religions

20세기 초반의 서방 세계의 독자들은 기독교 외의 종교에 대해서는 관심이 없었습니다.


21세기에 들어서서 세계는 더 작아졌고, 다른 종교에 대한 정보가 많아졌으며 테러리스트
들이 이슬람의 이름으로 항공기로 빌딩을 들이 박거나 하면서 서방 독자들의 관심을 끌게
되었다. 위의 분류 예를 보면 클래스와 클래스의 상속관계를 정의하는 소프트웨어 개발자
들이 미래를 예측하는 데에는 도서관 사서들보다 나을 것이 없다는 것을 보여 줍니다.
도서관 시스템 역시 계층적 분류에 기반하고 있습니다. 의회 도서관의 역사 영역의 분류 시
스템은 아래와 같습니다. (역시 셔키(Shirky)의 논문에서 발췌하였습니다.)
D: History (general)
DA: Great Britain
DB: Austria
DC: France

184
DD: Germany
DE: Mediterranean
DF: Greece
DG: Italy
DH: Low Countries

DJ: Netherlands

DK: Former Soviet Union


DL: Scandinavia
DP: Iberian Peninsula
DQ: Switzerland
DR: Balkan Peninsula

DS: Asia
DT: Africa

DU: Oceania
DX: Gypsies

네델란드의 역사가 아시아 전체와 아프리카 전체와 같은 수준으로 정리되어있습니다.


셔키(Shirky)가 지적한 것 처럼, 이 시스템은 보편적인 지식을 반영하지 않습니다. 의회 도
서관이 특정 분야에 관해 보유한 책의 수량에 기반하고 있을 뿐입니다.
"책장에 꽂혀있는 책"을 관찰해 보면 또 다른 문제를 찾을 수 있습니다. 특정 분야의 지식이
한 가지 일차원적인 분류법에 의해서 분류되어진다는 것입니다. 이런 틀에 박힌 방법은 (최
근까지는) 책장에 꽂힌 책의 형태로 보유한 모든 지식을 저장할 수 밖에 없었던 도서관에서
만 적용될 수 있습니다. 도서관 사서가 이 단일 분류법에 따라 책을 특정 책장을 선택하여
정리하는 것입니다. 왜냐하면 물리적인 책은 책장 한 곳에만 보관될 수 있기 때문입니다.
만약 예술의 역사에 관한 책이 있다면 도서관 사서는 이 책이 역사에관한 것인지, 예술에
관한것인지 결정하여 해당 책장에 꽂아야합니다.
셔키(Shirky)가 말했듯이 디지털 세상에서는 "책장이란게 없습니다". 이 말에 포함된 의미
는 더 이상 전문적인 사서가 필요 없으며, (상속 관계를 기반으로한) 표준 분류 시스템도 필
요 없으며, 더 나아가 카테고리 자체가 필요 없다는 뜻입니다. 개별 책에 덧붙여진 링크와
태그면 다 해결될 수 있습니다.
소프트웨어와 소프트웨어가 모델링하려고 하는 실 세계에는 책장이 없습니다. 도서관 사서
의 계층적인 분류 시스템이 지식을 표현하는 좋은 바탕이 되리라고 것은 잘 못된 시도입니
다.

185
자동적으로 공유되는 데이터
계층적 상속관계의 또 다른 문제점은 어떤 클래스와 그 클래스의 상위 클래스들 모두가 힙
(heap) 메모리에 데이터로 저장되어 있는 하나의 복사본에 대한 레퍼런스를 가진다는 것입니
다. 각각의 클래스에 포함되어 있는 메서드들은 근본적으로는 동시에 이 데이터에 접근할
수 있게 되어 동시 접근에 따른 전형적인 힘든 상황들을 만들어 냅니다. 락(lock)과 같은 메커
니즘을 통해 이런 문제를 방지할 수 있지만 안전하고 신뢰할 만한 수준으로 구현하는 것은
복잡하고 어려운 일입니다. 프로세싱 오버 헤드를 증가시키고 버그를 만들기도 합니다.

상속의 심한 간섭
상위 클래스가 하위클래스에 너무 심하게 간섭한다고 불평하는 것은 모순적인 면이 있습니
다. 왜냐하면, 객체지향 프로그래밍의 전도사들이 인캡슐레이션과 보안에 거의 과대망상적
으로 추종하도록 주장하였고 많은 언어(최근의 언어들에서 다소 완화되고 있다고 하더라
도)에 이러한 점들이 포함되었습니다. 그리고는 인캡슐레이션과 상속에 대한 고집이 객체
지향 프로그래밍의 근본 원칙인 것처럼 되어버렸습니다.

"객체지향 언어에서의 문제점은 이 언어들이 자신들이 존재할 수 있도록 하는 주변


의 모든 함축적인 환경을 가져가 버렸다는 점입니다. 여러분은 바나나를 원했는데
여러분이 가지게 된 것은 바나나를 가지게 된 것은 고릴라와 정글 전부입니다.”

– Joe Armstrong

객체간의 느슨한 연결(Loose coupling)은 인캡슐레이션의 원칙에 충실하기 위해 꼭 필요한 것으


로 간주되었습니다. 하지만 상속은 연관된 클래스의 인스턴스인 체들간의 느슨한 연결을
어렵게 만듭니다. 왜냐하면 상속, 구체적으로는 연속적으로 연결된 상속관계 사이에서 수
많은 프로퍼티와 메서드들의 상속관계를 만들어내고 프로그래머가 그 관계를 추적하거나
예측하는 것을 어렵게 만들어 버리고 이것이 빈번하게 버그의 원천이 됩니다.

2.프로토콜의 장점
프로토콜 지향 프로그램의 접근 방식은 많은 상황에서, 프로토콜을 구조체와 열거형과 함
께 사용하면 클래스를 사용함으로써 발생하는 부작용 없이 클래스가 할 수 있는 일들을 해
낼 수 있다는 생각을 기반으로 합니다.
스위프트 설계의 일부분으로 구조체와 열거형은 클래스가 하던 대부분의 일을 할 수 있게
개발되었습니다.

186
이 점은 구조체에서 가장 확실하게 볼 수 있는데 클래스와 아주 흡사하지만 결정적인 두 가
지 차이점이 있습니다. 구조체는 상속(과 상속에 따르는 부담)이 없으며 밸류타입(Value
Type)입니다. 구조체의 새 인스턴스는 데이터를 참조하는 대신에 복사합니다. 따라서 자동적
으로 공유되는 데이터의 문제가 발생하지 않습니다.
구조체는 클래스처럼 정보를 인캡슐레이션 할 수 있고 프로퍼티와 메서드와 같은 동작이
가능합니다. 클래스처럼 인스턴스를 만들 수 있습니다. 코드에 대한 접근제어 메커니즘을
제공합니다. 구조체 내의 코드에서 네임 충돌을 피할 수 있도록 네임스페이스를 제공합니
다. 추상화를 위한 근간을 제공하며 클래스처럼 익스텐션이 가능합니다.
열거형은 아마도 구조체 만큼은 아닐지 몰라도, 비슷한 능력을 제공합니다. 열거형도 구조
체와 동일하게 메서드와 인스턴스를 생성하는 능력이 있고 저장 프로퍼티와 비슷한 기능을
하는 연관 값(associated values)를 제공합니다. 열거형도 밸류타입이어서 레퍼런스타입이 가진
자동 공유 문제가 없습니다.
메서드와 프로퍼티의 구현을 정의할 수 있는 프로토콜 익스텐션은 다중 상속도 가능합니
다. 프로토콜은 클레이 셔키“Clay Shirky”가 말한 책장도 없고“no shelf”, 카테고리도 없는“no category”
처럼 동작합니다. 클래스 상속보다 프로토콜을 사용하는 것의 차이 점은 레스토랑에 가서
정해진 세트메뉴를 주문하는 것과 하나씩 원하는 것을 골라서 주문하는 것의 차이에 비유
할 수 있습니다.

3.프로토콜과 프로토콜 익스텐션


프로토콜이 할 수 있는 것을 정의하는 것과 "프로토콜 익스텐션"이 할 수 있는 것을 정의하
는 것을 구분하여 사용하기 어려울 수 있습니다. 후자는 extension 키워드와 이미 정의되
어 있는 프로토콜 이름를 사용하여 정의합니다.
프로토콜 정의에서는 이름과 인스턴스가 구현해야하는 프로퍼티와 메서드를 지정합니다.
인스턴스가 구현해야하는 메서드를 정의한다는 것은 메서드의 이름과 입력 파라미터와 리
턴값으로 이루어진 시그너쳐를 지정해야한다는 뜻이며, 구현해야하는 프로퍼티를 정의한
다는 것은 프로퍼티의 타입과 이름 그리고 프로퍼티가 읽기 전용인지, 읽고 쓰기가 허용되
는지를 정하는 것입니다. 이전에 정의된 하나 이상의 다른 프로토콜에 대응해야하는지에
대해서도 지정할 수 있고 이에 따라 구현해야하는 프로퍼티와 메서드들을 클래스처럼 상속
받습니다.
프로토콜 익스텐션 (이미 정의된 프로토콜의 익스텐션)은 언제나 추가적으로 구현되어야하
는 메서드들을 지정하게 됩니다. 스위프트2부터는 프로토콜 익스텐션에 하나 이상의 메서
드와 프로퍼티의 구현을 포함할 수 있게 되었습니다.

187
A Basic Protocol Extension
아래와 같은 프로토콜을 정의했다고 가정해 봅시다.
protocol TypicalSquirrel {
var nameOfIndividual: String { get }
var nameOfSpecies: String { get }
var skinHasFur: Bool { get }
var breathesAir: Bool { get }
var whyYouShouldNotPetThem: String { get }

func sayWhetherICanFly()
}

여기서 우리가 알고 싶어하는 다람쥐의 몇 가지 사항에 대해 정의하였습니다. 각각의 개체


(인스턴스)는 이름과 종의 이름 가지고 있습니다. { get } 또는 { get set }은 프로토콜
에 정의된 프로퍼티가 읽기 전용인지 또는 읽고 쓰기가 가능한지를 지정합니다. 다람쥐가
털이 있는지 여부와 숨을 쉬는지 왜 애완용으로 기르면 안 되는지를 지정할 수 있게 만들었
습니다.
이 프로토콜에는 sayWhetherICanFly() 함수를 구현해야하도록 정의하였습니다.
언제든 작성하는 프로그램이 이 값을 필요하긴 하겠지만 다람쥐를 정의할 때 마다 매 번
skinHasFur와 breathesAir를 구현하고 싶지 않을 것입니다. 날 수 있는 다람쥐가 흔한 것
이 아니므로 sayWhetherICanFly() 메서드를 매 번 구현하는 것도 성가신 일입니다.
따라서, TypicalSquirrel 프로토콜의 익스텐션을 아래와 같이 작성합니다.
extension TypicalSquirrel {
var skinHasFur: Bool { return true }
var breathesAir: Bool { return true }
var whyYouShouldNotPetThem: String {
return "They might have fleas with the plague"
}

func sayWhetherICanFly() {
print("I am a typical squirrel and I can walk and climb trees but no fly")
}
}

Mammal과 같은 큰 클래스로 부터 프로퍼티와 메서드를 상속받는게 낫지 않을까요? 물론 그


렇게 할 수 있습니다. 하지만 프로토콜 지향적인 프로그래밍은 클래스 상속을 하면 문제가

188
발생할 수 있다는 문제의식에서 부터 시작된 접근 방식입니다. 여기에 포유류에 관한 사항
들이 있는가요? 신뢰할 수 있고 구체적으로 의도를 가지고 구현된 작은 컴포넌트를 사용하
는 것이 더 안전합니다.
이제 다람쥐 인스턴스를 생성하기위한 Squirrel 구조체를 정의해 보겠습니다.
TypicalSquirrel 프로토콜을 적용하여 보통의 다람쥐가 가진 속성과 동작을 가져올 수 있
습니다.
struct Squirrel: TypicalSquirrel {
var nameOfIndividual: String = ""
var nameOfSpecies: String = ""
}

프로토콜이 요구하는 nameOfIndividual과 nameOfSpecies 프로퍼티를 이 구조체에 포함


시켰습니다. 여기에 초기 값도 지정하였습니다. 하지만 프로토콜 익스텐션에서 이미 정의
하고 있는 프로퍼티와 메서드는 포함시키지 않았습니다.
이제 다람쥐 "Pete" (Squirrel 구조체의 인스턴스)를 생성해 보겠습니다.
var pete = Squirrel(nameOfIndividual: "Pete" nameOfSpecies: "Gray Squirrel")

print(pete.nameOfSpecies) // 출력: Gray Squirrel


print(pete.skinHasFur) // 출력: true

print(pete.breathesAir) // 출력: true


print(pete.whyYouShouldNotPetThem) // 출력: They might have fleas with the plague

pete.sayWhetherICanFly() // 출력: I am a typical squirrel and I can walk and climb trees
but no fly

날다람쥐에 대한 처리
여기까지는 문제가 없었는데, 이제 높은 산을 올라 가다가 다른 종류의 “북부 날다람쥐”를
발견하였습니다.
이 종류는 정말 희귀한 종입니다. 아마 이 부근에서 발견할 수 있는 유일한 날다람쥐 종류
일 것입니다. 따라서 이 다람쥐를 위해서 프로토콜을 만드는 것은 적당하지 않으므로 그냥
FryingSquirrel 구조체를 만들도록 하겠습니다. 역시 TypicalSquirrel 프로토콜을 사용
하면서 몇 가지 것들을 오버라이드할 것입니다. whyYouShouldNotPetThem에 프로토콜 익

189
스텐션에서 정의 값과 다른 값을 적용하고 sayWhetherICanFly 메서드를 오버라이드하여
새롭게 구현하도록 하겠습니다.
struct FlyingSquirrel:TypicalSquirrel {
var nameOfIndividual: String = ""
var nameOfSpecies: String = ""
var whyYouShouldNotPetThem: String = "They are not friendly and have sharp teeth"

init (nameOfIndividual:String, nameOfSpecies:String) {


self.nameOfIndividual = nameOfIndividual
self.nameOfSpecies = nameOfSpecies
}

func sayWhetherICanFly() {
print("I am flying squirrel and I can walk and climb trees and also fly, or at
least glide")
}
}

이제 다람쥐 FlyingSquirrel의 인스턴스 "George"를 만들어 봅시다.


var george = FlyingSquirrel(nameOfIndividual: "George", nameOfSpecies: "Northern Flying
Squirrel")

print(george.nameOfSpecies) // 출력: Northern Flying Squirrel

print(george.skinHasFur) // 출력: true


print(george.breathesAir) // 출력: true

print(george.whyYouShouldNotPetThem) // 출력: They are not friendly and have sharp teeth

george.sayWhetherICanFly() // 출력: I am flying squirrel and I can walk and climb trees
and also fly, or at least glide

물론 TypicalFlyingSquirrel 프로토콜과 프로토콜 익스텐션을 만들어서 Squrrel에 한것


과 동일한 것을 할 수 있습니다. 하지만 수 천억개의 프로토콜을 만들고 싶지는 않을 것입
니다.

조건부 프로토콜
여러분이 캘리포니아 몬트레이에서 일하고 있는 생물학자라고 가정해 보겠습니다. 여러분
은 대부분의 시간을 바닷가 해변이나 보트위에서 보냅니다. 대부분의 장소에서 사람들이
볼 수 있는 포유류는 대부분 땅위에 살고 있습니다. 하지만 여기서는 반대의 상황입니다.

190
여기서 사람들 근처에서 볼수 있는 포유류는 대부분 바다에 살고 있습니다. 이것이 바로
"포유류"와 같은 매우 큰 크기의 복잡한 클래스를 사용하고 그 것을 상속받는 것에 대한 문
제점의 예시입니다.
따라서 우리는 대신 MarineMammal 프로토콜을 사용하겠습니다. 어떤 (바다사자나 물개같
은) 해양 포유류는 땅위를 걸을 수 있으므로 두번째 프로토콜인 CanWalkOnLand을 정의해
야 합니다.
몇 가지 동작을 정의하기 위해서 프로토콜 익스텐션을 사용할 것인데, 다만 이 동작들은 그
구조체가 두 프로토콜에 대응하는지 여부에 따라 달라집니다.
먼저 marineMammal 프로토콜을 정의하겠습니다.

protocol MarineMammal {
var nameOfIndividual: String { get }
var nameOfSpecies: String { get }
var skinHasFur: Bool { get }
var breathesAir: Bool { get }
}

매 번 해양 포유류를 구현할 때마다 필요한 몇 가지 기본적인 사항들을 위한 프로토콜 익스


텐션이 필요합니다.
extension MarineMammal {
var skinHasFur: Bool { return false }
var breathesAir: Bool { return true }
}

다음으로 CanWalkOnLand를 정의해 봅시다.


protocol CanWalkOnLand {
func iCanWalkOnLand()
}

CanWalkOnLand 프로토콜은 구현체가 iCanWalkOnLand() 메서드를 정의하도록 만들 뿐 구


현체를 제공하지는 않습니다.
이제 iCanWalkOnLand()의 구현체를 제공할 프로토콜 익스텐션을 만들겠습니다. 하지만 이
익스텐션은 CanWalkOnLand 프로토콜과 MarineMammal 프로토콜을 동시에 대응하는 경우
에만 해당됩니다.
extension MarineMammal where Self:CanWalkOnLand {
func iCanWalkOnLand() {

191
print ("I can walk on land.")
}
}

고래에 대한 구조체를 정의해 보겠습니다.


struct Whale: MarineMammal {
var nameOfIndividual: String = ""
var nameOfSpecies: String = ""
}

고래는 땅위를 걸을 수 없으므로 MarineMammal프로토콜에만 대응합니다. 따라서


iCanWalkOnLand 함수에 접근할 수 없습니다.

var cynthia = Whale(nameOfIndividual: "Cynthia", nameOfSpecies: "California Gray Whale")


print (cynthia.nameOfSpecies) // 출력: California Gray Whale

cynthia.iCanWalkOnLand() // 컴파일러 에러

바다사자와 물개에 대한 구조체를 정의해 보겠습니다.


struct SealsAndSeaLions: MarineMammal, CanWalkOnLand {
var nameOfIndividual: String = ""
var nameOfSpecies: String = ""
}

이 구조체는 MarineMammal 프로토콜과 CanWalkOnLoad 프로토콜을 동시에 따르고 있습니


다. 조건부 프로토콜 익스텐션에서 구현한 메서드 iCanWalkOnLand메서드를 사용할 수 있
습니다.
var sammy = SealsAndSeaLions(nameOfIndividual: "Sammy", nameOfSpecies: "Harbor Seal")
print(sammy.nameOfSpecies) // 출력: Harbor Seal

sammy.iCanWalkOnLand() // 출력: I can walk on land.

중복된 메서드를 정의한 다중 프로토콜에 대한 대응


만약 같은 메서드를 중복 구현한 여러 개의 프로토콜의 익스텐션들을 사용하면 어떻게 될
까요?
예를 들어 Mammal프로토콜과 MarineMammal프로토콜을 정의하고 두 프로토콜 모두
sayCanIWalkOnLand 메서드를 요구하고 있고 두 프로토콜의 익스텐션을 각각 구현하였습
니다.

192
Mammal의 경우에는 이 메서드는 아래와 같고

func sayCanIWalkOnLand() {
print("I am a Mammal and I CAN walk on land")
}

MarineMammal의 경우에 메서드는 아래와 같습니다.

func sayCanIWalkOnLand() {
print("I am a Marine Mammal and I CANNOT walk on land")
}

그 다음으로 HarborSeal 구조체를 정의한 후 인스턴스를 생성하고 이 메서드를 호출한다


면 어떤 일이 벌어질까요? 어느 메서드가 호출되어 실행이 될까요?
정답은 스위프트에서 이렇게 중복된 이름의 함수를 가진 복수의 프로토콜들을 따르게 되는
구조체는 만들 수 없다 입니다. 컴파일러 에러가 발생하게 됩니다.
protocol Mammal {
func sayCanIWalkOnLand()
}

protocol MarineMammal {
func sayCanIWalkOnLand()
}

extension Mammal {
func sayCanIWalkOnLand() {
print("I am mammal and I CAN walk on land")
}
}

extension MarineMammal {
func sayCanIWalkOnLand() {
print("I am a Marine Mammal and I CANNOT walk on land")
}
}

struct HarborSeal: Mammal, MarineMammal { // 컴파일러 에러 발생


}

193
스위프트 표준 라이브러리의 익스텐션
가장 자주 프로토콜 익스텐션이 사용되는 곳이 스위프트의 표준 라이브러리나 써드파티
(Third-party) 라이브러리의 메서드를 제공하기 위해서입니다.
하나의 타입뿐만 아니라 여러개의 타입에 기능을 추가하는 것이 한 번에 가능해졌습니다.
예를 들어 CollectionType 타입의 프로토콜에 익스텐션을 사용하면 추가된 기능은 배열과
딕셔너리와 집합 모두에 (이 데이터 타입들은 CollectionType 프로토콜을 따르고 있으므
로)에 적용됩니다.

프로토콜의 셀프 요구사항
프로토콜과 구조체가 클래스에 비해 더 우월한 경우가 있다는 아브라함의 주장에서 보여준
예제를 보면, 두 개의 값이 어떤 순서로 정렬되어야 하는지 결정하는 함수의 예가 있었습니
다. 배열을 정렬하거나 배열에서 바이너리 검색을 하는 데에 보통 이런 종류의 함수가 사용
됩니다. 이런 정렬을 하는 일반적인 솔루션을 만드는 것은 어려운 문제입니다. 왜냐하면 어
떤 타입의 값을 정렬하는 적절한 방법이 한 가지 이상의 방법이 있기 때문입니다. (정수
5는 정수 4보다 항상 큽니다. 하지만 문자열 “05”와 “4”의 경우에는 어떤 순서로 정렬해야
하는가?라는 문제가 남습니다.)
아브라함이 선택한 솔루션은 프로톨과 구조체를 사용하는 것이다.
protocol Ordered {
func precedes(other: Self) -> Bool
}

struct Number : Ordered {


var value: Double = 0

func precedes(other: Number) -> Bool {


return self.value < other.value
}
}

여기서 프로토콜은 인스턴스를 생성하는 타입 (여기서는 구조체)이 프로토콜에 따라 주어


진 메서드의 시그너처를 구현하도록 지정하고 있습니다. 이 함수 시그너쳐는 입력 파라미
터의 타입이 Self로 되어있는데 이 입력 파라미터는 프로토콜을 구현하는 구조체의 타입과
동일해야한다는 것을 의미합니다. 이 예에서는 Number 타입이어야 합니다.

194
스위프트에서 소문자 self는 현재 인스턴스 자신에 대한 레퍼런스이며, 첫 글자가 대문자
인 Self는 인스턴스를 생성하는 타입에 대한 레퍼런스입니다. where절에 사용되어 단순히
셀프 요구사항(Self requirement)를 설정하거나 현재 타입을 뜻할 수 있습니다.

195

You might also like