본문 바로가기
🍎 iOS/DevNote

[Swift] 프로토콜(Protocol) 개념 정리

by @Eddy 2023. 5. 26.
728x90

프로토콜(Protocol)이란?

Swift Language Guide를 보면, 프로토콜에 대한 정의가 아래와 같이 적혀있다.

 

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. 
프로토콜은 특정 작업이나 기능에 적합한 메서드, 속성, 그 외 필수요소의 청사진을 정의한다.

 

그리고 프로토콜은 일급 객체이며, 기본적으로 규약, 협약이라는 의미를 가진다.

일급객체란? (쉽게 말해 변수나 상수처럼 사용 가능하다는 의미)
- 변수에 할당 가능
- 함수의 파라미터로 전달 가능
- 반환값으로 사용 가능

규약과 협약은 '~을 해야한다' 또는 '~에 대한 정의'가 되어있고,

실제 실행은 해당 규약과 협약을 맺은 공동체(행위의 주체)가 한다.

프로토콜도 동일하게, 단순히 무엇을 해야하는지, 무엇을 정의해야하는지에 대한 요구사항만을 담고, (청사진의 의미)

구현은 프로토콜을 채택한 클래스, 구조체, 열거형에서 한다.

 

'뭔 소리야?' 싶을 수 있는데, 예제 코드를 보며 이해해보자.

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의 클래스는 일부 다른 언어와는 달리 단일 상속만 가능하며, 당연히 다중 상속은 불가하다.
  • 따라서 여러 클래스의 속성과 메서드를 상속받기 위해서는 클래스 상속 계층이 쌓거나, 구조를 잘 설계해야 한다.

Swift에서 Class 다중 상속 예시

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

클래스 상속 계층(좌 - 코드, 우 - 구조)

  • 이와 같은 구조의 문제는, 상위 클래스의 구조를 그대로 따라야하기에 불필요한 속성과 메서드를 가져올 수 있다는 것이다.

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패턴 모두 프로토콜을 활용한 방식이기 때문에 디자인패턴을 다룰 때에도 알아야하는 문법이니 꼭 알고 넘어가야한다.

 

 

참고자료

반응형

댓글