study record
[Swift] 프로토콜 본문
*이 글은 책 ‘스위프트 프로그래밍’을 학습하고 작성한 글입니다.
프로토콜이란
프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의한다. 구조체, 클래스, 열거형은 프로토콜을 채택해서 특정 기능을 실행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있다. 어떤 프로토콜의 요구사항을 모두 따르는 타입은 해당 프로토콜을 준수한다고 표현한다. 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 한다. 즉, 프로토콜은 정의를 하고 제시를 할 뿐이지 스스로 기능을 구현하지는 않는다.
프로토콜 채택
프로토콜은 구조체, 클래스, 열거형의 모양과 비슷하게 정의할 수 있으며 protocol 키워드를 사용한다. 구조체, 클래스, 열거형 등에서 프로토콜을 채택하려면 타입 이름 뒤에 콜론(:)을 붙여준 후 채택할 프로토콜 이름을 쉼표(,)로 구분하여 명시해준다. 만약 클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 다음에 채택할 프로토콜을 나열해준다.
프로토콜 요구사항
프로토콜은 타입이 특정 기능을 실행하기 위해 필요한 기능을 요구한다. 프로토콜이 자신을 채택한 타입에 요구하는 사항은 프로퍼티나 메서드와 같은 기능들이다.
프로퍼티 요구
프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있다. 그렇지만 프로토콜은 그 프로퍼티의 종류(연산 프로퍼티인지, 저장 프로퍼티인지 등)는 다로 신경쓰지 않는다. 프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현해주면 된다. 다만 프로퍼티를 읽기 전용으로 할지 혹은 읽고 쓰기가 모두 가능하게 할지는 프로토콜이 정해야 한다.
프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의한다. 읽기와 쓰기가 모두 가능한 프로퍼티는 프로퍼티의 정의 뒤에 { get set }이라고 명시하며, 읽기 전용 프로퍼티는 프로퍼티의 정의 뒤에 { get }이라고 명시해 준다.
protocol SomeProtocol {
var settableProperty: String { get set }
var notNeedToBeSettableProperty: String { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
static var anotherTypeProperty: Int { get }
}
SomeProtocol은 읽기와 쓰기 모두를 요구하는 프로퍼티와 읽기만 가능하다면 어떻게 구현되어도 상관 없다는 요구사항이다.
타입 프로퍼티를 요구하려면 static 키워드를 사용한다. 클래스의 타입 프로퍼티에는 상속 가능한 타입 프로퍼티인 class 타입 프로퍼티와 상속 불가능한 static 타입 프로퍼티가 있습니다만 이 두 타입 프로퍼티를 따로 구분하지 않고 모두 static 키워드를 사용하여 타입 프로퍼티를 요구하면 된다.
프로토콜에서 요구한 프로퍼티는 읽기 가능한 프로퍼티였지만 실제로 프로토콜을 채택한 클래스에서 구현할 때는 읽고 쓰기가 가능한 프로퍼티로 구현해도 전혀 문제가 없다.
메서드 요구
프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수 있다. 프로토콜이 요구할 메서드는 프로토콜 정의에서 작성한다. 다만 메서드의 실제 구현부인 중괄호 부분은 제외하고 메서드의 이름, 매개변수, 반환 타입 등만 작성하며 가변 매개변수도 허용한다.
프로토콜의 메서드 요구에서는 매개변수 기본값을 지정할 수 없다. 타입 메서드를 요구할 때는 타입 프로퍼티 요구와 마찬가지로 static 키워드를 명시한다. static 키워드를 사용하여 요구한 타입 메서드를 클래스에서 실제 구현할 때는 static 키워드나 class 키워드를 사용해도 무방하다.
protocol Receiveable {
func received(data: Any, from: Sendable)
}
protocol Sendable {
var from: Sendable { get }
static func isSendableInstance(_ instance: Any) -> Bool
}
가변 메서드 요구
가끔은 메서드가 인스턴스 내부의 값을 변경할 필요가 있다. 값 타입의 인스턴스 메서드에서 자신 내부의 값을 변경하고자 할 때는 메서드의 func 키워드 앞에 mutating 키워드를 적어 메서드에서 인스턴스 내부의 값을 변경한다는 것을 확실히 해준다.
프로토콜이 어떤 타입이든 간에 인스턴스 내부의 값을 변경해야 하는 메서드를 요구하려면 프로토콜의 메서드 정의 앞에 mutating 키워드를 명시해야한다. 참조타입인 클래스의 메서드 앞에는 mutating 키워드를 명시하지 않아도 인스턴스 내부 값을 바꾸는데 문제가 없지만 값 타입인 구조체와 열거형의 메서드 앞에는 mutating 키워드를 사용한 메서드 요구가 있다고 하더라도 클래스 구현에서는 mutating 키워드를 써주지 않아도 된다.
protocol Resettable {
mutating func reset()
}
class Person: Resettable {
var name: String?
var age: Int?
func reset() {
self.name = nil
self.age = nil
}
}
struct Point: Resettable {
var x: Int = 0
var y: Int = 0
mutating func reset() {
self.x = 0
self.y = 0
}
}
값 타입인 구조체에서는 mutating 키워드를 포함하여 구현하였고 클래스에서는 키워드를 제외하고 구현했다. 만약 프로토콜에서 가변 메서드를 요구하지 않는다면 값 타입의 인스턴스 내부 값을 변경하는 mutating 메서드는 구현이 불가능하다.
이니셜라이저 요구
프로토콜은 프로퍼티, 메서드와 맟나가지로 특정한 이니셜라이저를 요구할 수도 있다. 구조체는 상속할 수 없기 때문에 이니셜라이저 요구에 대해 크게 신경쓸 필요가 없다. 클래스 타입에서는 이니셜라이저를 구현할 때 이니셜라이저가 지정인지 편의 이니셜라이저인지는 크게 중요하지 않다. 그러나 이니셜라이저를 구현할 때 required 식별자를 붙인 요구 이니셜라이저로 구현해야 한다.
만약 클래스가 상속받을 수 없는 final 클래스라면 required 식별자를 붙일 필요가 없다. 만약 클래스에 프로토콜이 요구하는 이니셜라이저가 이미 구현되어 있는 상황에서 그 클래스를 상속받았다면 required와 override 모두 명시해야 한다.
프로토콜은 실패 가능한 이니셜라이저를 요구할 수도 있다. 실패 가능한 이니셜라이저를 구현 시에 실패가능한 이니셜라이저로 구현해도, 일반적인 이니셜라이저로 구현해도 무방하다.
프로토콜의 선택적 요구
프로토콜의 요구사항 중 일부를 선택적 요구사항으로 지정할 수 있다. 이를 위해서는 objc 속성이 부여된 프로토콜이어야 한다. objc 속성이 부여되는 프로토콜은 Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있다. 따라서 열거형이나 구조체에서는 objc 속성이 부여된 프로토콜은 아예 채택할 수 없다.
선택적 요구를 하면 프로토콜을 준수하는 타입에 해당 요구사항을 필수로 구현할 필요가 없다. 선택적 요구사항은 optional 식별자를 요구사항의 정의 앞에 붙여주면 된다. 메서드나 프로퍼티를 선택적 요구사항으로 하게 되면 자동적으로 타입은 옵셔널이 된다.
import Foundation
@objc protocol Moveable {
func walk()
@objc optional func fly()
}
'Swift > 스위프트 프로그래밍' 카테고리의 다른 글
[Swift] 제네릭 (0) | 2022.01.14 |
---|---|
[Swift] 익스텐션 (0) | 2022.01.12 |
[Swift] 타입 캐스팅 (0) | 2022.01.06 |
[Swift] 상속 (0) | 2021.12.27 |
[Swift] 서브스크립트 (0) | 2021.12.26 |