[Swift] 프로토콜 (Protocols)
프로토콜은 메소드, 프로퍼티, 요구사항의 청사진을 정의합니다. 프로토콜은 클래스, 구조체, 열거형에 의해 채택될 수 있으며 이 때 프로토콜이 요구하는 사항을 모두 충족하면 해당 프로토콜을 준수한다고 합니다.
준수해야 하는 타입의 요구사항을 정의하는 것 외에도 요구사항의 일부를 구현하거나, 준수하는 타입에 추가 기능을 구현하기 위해 프로토콜을 확장할 수 있습니다.
프로토콜 구문
프로토콜은 클래스, 구조체, 열거형과 비슷한 방법으로 선언할 수 있습니다.
프로퍼티, 메소드 요구사항
프로퍼티
- 프로퍼티는 타입과 이름만 지정
- 연산 프로퍼티인지, 저장 프로퍼티인지는 지정하지 않음
- 타입 프로퍼티는 항상
static
키워드 사용
- gettable / settable 여부 작성
- 항상
var
로 선언
메소드
- 메소드명과 리턴값 지정, 내용은 작성하지 않음
- 타입 메소드는 항상
static
키워드 사용
메소드 요구사항 변경
인스턴스를 수정해야할때 value type
인스턴스라면 func
앞에 mutating
키워드를 이용해 메소드가 속한 인스턴스와 프로퍼티를 수정할 수 있습니다.
생성자 요구사항
프로토콜에서는 필수로 구현해야하는 생성자를 지정할 수 있습니다. 메소드와 마찬가지로 내용은 작성하지 않습니다.
생성자가 있는 프로토콜을 채택한 타입에서는 생성자 앞에 required
키워드를 작성해야합니다. 서브 클래스가 슈퍼 클래스의 생성자를 override
하고 프로토콜에서 요구하는 생성자도 구현하는 경우 required
와 override
키워드를 모두 사용합니다. 즉, 슈퍼 클래스는 프로토콜은 채택하지 않았는데 서브 클래스에서는 프로토콜을 채택한 경우 다음과 같이 구현합니다.
타입으로서의 프로토콜
프로토콜 자체에서는 기능을 구현하지 않지만 프로토콜을 타입으로 사용할 수 있습니다. 프로토콜을 타입처럼 사용하는 것을 existential type 이라고 하는데, 이는 "T
가 프로토콜을 준수하도록 하는 타입 T
가 존재한다" 라는 뜻입니다.
다음을 포함하여 다른 타입이 허용되는 곳에서 프로토콜을 이용할 수 있습니다.
- 함수, 메소드, 생성자의 파라미터 타입 혹은 리턴 타입
- 상수, 변수, 프로퍼티의 타입
- 배열, 딕셔너리 등의 항목 타입
generator
프로퍼티는 RandomNumberGenerator
타입입니다. 따라서 RandomNumberGenerator
프로토콜을 채낵하는 모든 타입에 인스턴스로 설정할 수 있습니다. Dice
클래스의 내부 코드는 이 프로토콜이 준수하는 모든 인스턴스에 적용되는 방식으로만 generator
를 사용할 수 있습니다. 즉, generator
의 기본 타입에 정의된 메소드나 프로퍼티를 사용할 수 없습니다.
하지만 DownCasting 을 이용해 슈퍼 클래스를 서브 클래스로 다운 캐스트 하는것처럼 프로토콜 타입을 기본 타입으로 다운 캐스트 해서 사용할 수는 있습니다.
Dice
가 제공하는 roll
메소드에서는 random()
메소드를 호출하고 있습니다. generator
는 RandomNumberGenerator
프로토콜을 채택하기 때문에 random()
메소드를 호출할 수 있습니다.
Delegation
Delegate 패턴은 클래스 또는 구조체가 권한의 일부를 다른 타입의 인스턴스에 넘겨주거나 위임할 수 있도록 하는 디자인 패턴입니다.
Delegate 패턴을 사용하는 이유는 다음과 같이 정리할 수 있습니다.
- 한 클래스와 다른 클래스의 상호작용을 간단히 할 수 있도록 도움
- 1:1 관계에서 유용
- 프로토콜을 준수하는 것만으로 구현이 가능하기 때문에 유연함
Extension 으로 프로토콜 준수성 추가
새로운 프로토콜을 채택하고 준수하기 위해 기존 타입을 확장할 수 있습니다. 확장을 이용해 기존 타입에 새로운 프로퍼티, 메소드, 서브스크립트를 추가할 수 있으므로 프로토콜의 요구사항을 추가할 수 있습니다.
조건적으로 프로토콜 준수
일반 타입은 타입의 파라미터가 프로토콜을 준수하는 경우와 같은 특정 조건에서만 프로토콜의 요구사항을 충족시킬 수 있습니다.
타입을 확장할 때 제약조건을 나열하여 일반 타입이 프로토콜을 조건적으로 준수할 수 있도록 만들 수 있습니다. 이 때 where
구문을 사용하여 제약조건을 작성합니다.
확장과 함께 프로토콜 채택 선언
타입이 이미 프로토콜의 모든 요구사항을 준수하지만 해당 프로토콜을 채택한다고 명시하지 않은 경우, 비어있는 확장을 사용하여 프로토콜을 채택하도록 할 수 있습니다.
합성 구현을 이용한 프로토콜 채택
Swift 는 여러 경우에 Equatable
Hashable
Comparable
에 대한 프로토콜의 요구사항을 자동으로 준수합니다. 이러한 합성 구현을 사용하면 프로토콜의 요구사항을 직접 구현하기 위해 반복적인 코드를 작성할 필요가 없습니다.
다음 종류의 커스텀 타입에 대해 Equatable
합성 구현을 사용할 수 있습니다.
Equatable
을 준수하는 저장 프로퍼티만 있는struct
Equatable
을 준수하는 associated type 만 있는enum
- associated type 이 없는 enum
==
의 합성구현을 사용하려면 ==
연산자를 직접 구현하는 것이 아니라 코드에 Equatable
프로토콜을 채택해야 합니다. Equatable
은 !=
의 기본 구현을 제공합니다.
다음과 같은 커스텀 타입에 대해 Hashable
합성 구현을 사용할 수 있습니다.
Hashable
을 준수하는 저장 프로퍼티만 있는struct
Hashable
을 준수하는 associated type 만 있는enum
- associated type 이 없는 enum
hash(into:)
의 합성 구현을 사용하려면 hash(into:)
메소드를 직접 구현하지 않고 코드에 Hashable
프로토콜을 채택합니다.
Swift 는 원시값이 없는 열거형에 대해 Comparable
의 합성구현을 제공합니다. 열거형에 associated type 이 있는 경우 모두 Comparable
을 준수해야 합니다.
<
의 합성구현을 수신하려면 <
연산자를 직접 구현하지 않고 원래 열거형이 선언된 코드에 Comparable
을 채택합니다. Comparable
의 기본 구현인 <=
>
>=
는 나머지 비교 연산자를 제공합니다.
프로토콜 상속
프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다.
프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 프로토콜은 클래스와 다르게 다중 상속이 가능합니다.
Protocol Composition
동시에 여러 프로토콜을 준수하는 타입을 사용하는 것이 유용할 수 있습니다. 프로토콜 Composition 을 사용하여 여러개의 프로토콜을 하나의 프로토콜로 결합할 수 있습니다. 이렇게 되면 Composition 에 있는 모든 프로토콜의 요구사항이 결합된 임시 로컬 프로토콜을 정의한 것처럼 작동합니다. 이는 새로운 프로토콜 타입을 정의하지는 않습니다.
프로토콜 준수성 검사
프로토콜 준수성에 대해 확인하고 특정 프로토콜로 검사하기 위해 is 와 as 연산자를 사용할 수 있습니다. 프로토콜을 확인하고 캐스팅하는 것은 타입을 확인하고 캐스팅하는 것과 정확하게 같은 구문을 따릅니다.
is
연산자는 인스턴스가 프로토콜을 준수한다면true
를 리턴하고 그렇지 않으면false
를 리턴합니다.- 다운 캐스트 연산자의
as?
버전은 프로토콜 타입의 옵셔널 값을 리턴하고 인스턴스가 프로토콜을 준수하지 않으면 그 값은nil
입니다. - 다운 캐스트 연산자의
as!
버전은 프로토콜 타입으로 강제 다운 캐스팅하고 다운 캐스트가 성공하지 못하면 런타임 에러를 트리거 합니다.
Circle
과 Country
는 HasArea
를 준수하지만 Animal
은 그렇지 않습니다. 그렇기 때문에 if let
구문에서 Circle
과 Country
인스턴스는 area
프로퍼티를 리턴하지만 Animal
인스턴스는 nil
을 리턴합니다.
프로토콜 확장
프로토콜은 타입을 준수하는 타입에 제공하기 위해 메소드, 생성자, 서브 스크립트, 그리고 연산 프로퍼티 구현을 확장할 수 있습니다. 이를 통해 각 타임의 개별 적합성 혹은 전역 함수가 아닌 프로토콜 자체에 동작을 정의할 수 있습니다.
RandomNumberGenerator
프로토콜은 Bool
값을 리턴하기 위해 필요한 random()
메소드의 결과를 사용하는 randomBool()
메소드를 제공하기 위해 확장될 수 있습니다.
프로토콜 확장을 생성함으로써 프로토콜을 준수하는 모든 타입은 추가 수정 없이 메소드 구현을 자동으로 얻게 됩니다. 프로토콜 확장은 타입에 구현을 추가할 수는 있지만 다른 프로토콜을 상속할 수는 없습니다. 프로토콜 상속은 항상 프로토콜 선언 자체에 지정되어야 합니다.
프로토콜 확장에 제약사항 추가
프로토콜 확장을 정의할 때 메소드와 프로퍼티를 사용하기 전에 준수해야 하는 제약조건을 지정할 수 있습니다. where
절을 사용하여 확장할 프로토콜의 이름 뒤에 제약조건을 작성할 수 있습니다.
예를 들어 Equatable
을 준수하는 항목의 모든 컬렉션에 적용하는 Collection
프로토콜의 확장을 정의할 수 있습니다. 이를 적용하면 두 요소간의 같음과 다름에 대한 확인을 위해 ==
와 !=
연산자를 사용할 수 있습니다.
참고 링크
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
https://bbiguduk.gitbook.io/swift/language-guide-1/protocols
'iOS 개발 > Swift' 카테고리의 다른 글
[Swift] 싱글톤 패턴 (Singleton Pattern) (0) | 2022.05.20 |
---|---|
[Swift] 타입 캐스팅 (Type Casting) (0) | 2022.05.19 |
[Swift] 확장 (Extensions) (0) | 2022.05.13 |
[Swift] ARC (2/2) (0) | 2022.04.29 |
[Swift] ARC (1/2) (0) | 2022.04.29 |