Swift의 프로토콜은 무엇인가, 어떻게 활용하나
- 프로토콜의 요구사항은 무엇인가
- 프로토콜 확장(Protocol Extension)을 사용하는 이유는?
- 프로토콜 지향 프로그래밍(Protocol-Oriented Programming)의 장점은?
- 상속(Inheritance)과 프로토콜(Protocol)의 차이점은?
- 클래스 상속을 사용할 때의 장단점은?
- 다중 상속(Multiple Inheritance)이 불가능한 이유는?
- 프로토콜 준수(Conformance)를 통해 다형성을 구현하는 방법은?
Swift의 특징
Swift는 2014년 애플이 발표한 언어로 현재(2025) Swift 6 까지 버전이 나와있다.
Apple 생태계에서 사용될뿐만 아니라 서버, 아두이노 등 활발하게 영역을 넓히고 있다.
Apple이 발표한 스위프트의 언어적 특성은 총 3가지가 있다.
- Safe (안정성)
- Fast (신속성)
- Expressive (더 나은 표현)
또한 멀티 패러다임 프로그래밍 언어라고 한다.
- 객체지향 프로그래밍
- 함수형 프로그래밍
- 프로토콜 지향 프로그래밍

이중에서 프로토콜 지향 프로그래밍에 대해서 알아보자.
프로토콜 지향 프로그래밍 (POP)
Swift 2.0 버전에 protocol
,extension
이 추가되며 지원되게 되었다.
객체지향 프로그래밍의 단점들을 해결하기 위해 주목받고 있는 기법으로
객체지향 프로그래밍의 단점
불필요한 요소까지 상속
SubClass는 SuperClass의 불필요한 변수와 함수를 상속받아야함.
참조 타입으로만 사용가능
상속받으면 무조건 참조타입으로 생성되기 때문에 참조비용이 발생
필요한 부분만을 프로토콜로 분리해 프로그래밍 하는 것을 말한다.
프로토콜은 특정 작업이나 기능을 실행하기 위한 메소드, 프로퍼티, 그리고 다른 요구사항들의 청사진을 정의할 수 있다.
클래스, 구조체, 열거형, 타입 등 모두 프로토콜을 채택해 정의된 요구사항을 실제로 구현하고있다.
프로토콜 지향 프로그래밍의 특징
- 다중 상속의 한계 극복
- 클래스는 단일 상속의 원칙을 따른다
- 하지만 프로토콜은 여러개를 채택해 구현이 가능하다.
- 값 타입 사용가능
- 클래스를 상속받아 사용하면 무조건 참조타입으로 생성해 비용이 많이 발생하지만, 프로토콜은 값, 참조 타입 모두 지원한다.
- 값 타입 사용시 상속을 할 수 없던 한계를 극복
- 필요한 기능만 구현
- 불필요한 API를 제외, 정의한 API만 가져와 구현이 가능해짐.
- 무엇을 구현해야 하는지가 명확해짐.
장점
- 코드 중복 최소화
- 가볍고 안전하다
- 상속과 달리 필요한 것만 골라서 쓸 수 있음
- 값 타입의 상속효과
- 객체지향 상속의 단점을 해결
- 수평적인 확장 가능
- 제네릭 활용
위 장점들 처럼 코드의 재사용성, 유지보수성을 향상시킬 수 있고 단위 테스트시 Mock Object를 쉽게 만들어 테스트에 용이성도 갖추고 있다. 또한 결합도를 낮춤으로서 유연성과 확장성을 갖추는것도 있음.
프로토콜 예제
프로토콜 생성
protocol Car {
var fuel: Int { get set }
func move(speed: Int)
}
프로토콜을 채택할 Class나 Struct 등에 공통으로 사용할 메서드나 프로퍼티를 정의해 생성하면 된다.
프로퍼티, 메서드에 대한 껍데기만 제공되고 채택한곳에서 실제 구현을 하면 된다.
프로토콜을 채택하게되면 정의한 것을 무조건 구현해야한다.
만약 프로토콜을 채택할 때 꼭 정의하지 않아도 되지만 필요한곳에는 있어야하는 경우?
일때는 프로토콜의 메서드나 프로퍼티를 optional로 선언할 수 있다.
@objc protocol Car {
@objc optional var fuel: Int { get set }
func move(speed: Int)
}
@objc optional
을 붙이면 가능하고 추가로, 프로토콜에도 @objc
를 붙여줘야 사용할 수 있다.
프로토콜 메서드를 정의할 때 struct에 채택되면서 mutating
을 붙여줘야 할 때는 protocol에서 mutating
을 붙여주면 됩니다.
@objc가 프로토콜에 있을때는 클래스 전용일 때 사용하는 AnyObject도 자동으로 채택되기 때문. (프로토콜에도 프로토콜을 채택할 수 있다.)
프로토콜 extension
프로퍼티, 메서드에 대한 껍데기만 제공되고 정의는 불가능하지만, extension을 쓰면 프로퍼티의 기본값, 메서드의 동작을 정의할 수 있다.
extension Car {
var fuel: Int { return 0 }
func move(speed: Int) {print("\(speed)Km/h moving...")}
}
만약 정의한 프로토콜 extension 을 특정 프로토콜을 채택한 것만 사용할 수 있게 제약을 걸려면 아래 구문을 추가하면 된다.
extension Car: where Self: Mechanic
프로토콜 Generic 사용하기
associatedtype
키워드를 사용해서 범용 타입 구현이 가능하다!
protocol Stack {
associatedtype value
func push(value: value)
func pop() -> value
}
만약 타입에 대해 제약이 필요하다면! 아래 구문처럼 제약을 추가하면 된다.associatedtype value: Equatable
사실 기존 Generic 방식으로도 프로토콜에 범용타입을 적용할 수 있는데, 메서드만!!
protocol Stack {
func push<T: Equatable>(value: T)
}
메서드를 선언할 때는 제네릭을 사용하는것이 가능하다.
프로토콜의 Default 값
프로토콜의 메서드에 바로 Default값을 설정할 수는 없지만, extension에 추가로 정의하면 default값을 정의할 수 있다.
extension Car {
func move(speed: Int = 0) {...}
}
만약 프로토의 메서드가 associatedtype
을 가지고 있을때는 기본값 설정이 불가능하지만, associatedtype
에 특정 프로토콜을 채택하거나 extension을 특정 프로토콜을 따를 경우에만 사용할 수 있게 정의한다면 범용타입도 기본값을 가질 수 있게 할 수 있다!!
protocol Stack {
associatedtype value: BinaryInteger
func push(value: value)
//또는 이렇게도 가능
func push<T: Equatable>(value: T = 1) {}
}
extension Stack { func push(value: value = 1) {...} }
위 방법의 경우 프로토콜을 채택해서 구현할 때, associatedtype
을 정의할 때 무조건 BinaryInteger
를 따르는 타입만을 사용할 수 있다.
extension Stack where value: BinaryInteger {
func push(value: value = 1) {...}
}
하지만 extension에 제약을 걸어두면 associatedtype
을 정의할 때 BinaryInteger
타입을 채택하는게 아니라도 오류가 뜨진 않지만, extension에 정의한 기본값을 사용할수는 없다.
프로토콜 채택
class BMW: Car {
var fuel: Int = 0
func move(speed: Int) {
print("이동")
}
}
저장프로퍼티의 경우 let으로도 변경해서 구현할 수 있다. (연산 프로퍼티는 안됌 당연히)
struct VStack: Stack {
typealias value = Int
func push(value: value) { }
}
associatedtype
의 경우는 채택시 typealias
로 타입을 명시해준다
물론 타입이 추론 가능한 경우는 꼭 typealias
를 안해줘도 컴파일러가 추론을 해준다.
진짜 시리즈로 프로토콜 설명해주시는데 너무 알차다
ref: https://babbab2.tistory.com/174