(내가 보려고 만든 번역)
(오역이 있다면 제보 부탁합니다)
링크: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols
Protocols
-> 적합한 유형을 구현해야하는 요구사항을 정의한다.
- 프로토콜은 특정 작업이나 기능에 적합한 메소드와 프로퍼티들,그리고 다른 요구사항들의 청사진을 정의한다.
- 프로토콜은 해당 요구사항의 실제 구현을 제공하기 위해 클래스, 구조체, 열거형에 채택할 수 있다.
- 프로토콜의 요구사항을 충족하는 모든 유형은 해당 프로토콜을 준수한다고 한다.
구현해야하는 요구사항을 지정하는것 외에도 프로토콜을 확장할 수 있다.
- 요구사항의 일부를 구현
- 추가기능 구현
Protocol Syntax
클래스, 구조체, 열거형에 모두 유사한 방식으로 프로토콜을 정의
protocol SomeProtocol {
// protocol definition goes here
}
이름 뒤에 콜론으로 구분하여 프로토콜을 배치할 수 있고, 채택한다고 명시한다.
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
클래스가 슈퍼클래스가 있는 경우, 채택하는 프로토콜 앞에 슈퍼클래스를 나열하고 그 뒤로 쉼표로 구별하여 추가한다.
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
- 프로토콜은 타입이기때문에 이름을 대문자로 시작한다.
Property Requirements
- 프로토콜은 인스턴스 프로퍼티나 특정 이름과 타입의 프로퍼티를 제공하기위해 모든 알맞은 타입을 요구할 수 있다.
- 프로토콜은 프로퍼티가 저장 프로퍼티인지, 계산 프로퍼티인지 지정하지 않고, 필수 프로퍼티 이름과 타입만 지정한다.
- 또한 각 프로퍼티가 gettable이어야하는지 gettable 및 settable이어야 하는지 지정한다.
- 프로토콜이 gettable 및 settable 프로퍼티을 요구하는 경우, 해당 속성은 상수 저장 프로퍼티나, 읽기 전용 계산된 프로퍼티로 충족될 수 없다.
- 프로토콜이 gettable 프로퍼티만 요구하는 경우, 모든 종류의 프로퍼티가 가능하며, 자신의 코드에 맞게 프로퍼티도 설정이 가능하다?
- 프로퍼티는 항상 키워드 var 접두사가 붙은 변수 프로퍼티으로 선언된다.
- gettable 및 settable은 { get set }을, gettable 프로퍼티는 { get }을 유형 선언뒤에 표시한다.
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
- 프로토콜 내에서 static 키워드는 항상 접두사 키워드 앞에 붙인다.
- 이 규칙은 클래스에서 구현될때도 타입 프로퍼티 앞에 class or static 키워드가 붙는 경우에도 적용된다.
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
단일 인스턴스 프로퍼티가 있는 프로토콜의 예시
protocol FullyNamed {
var fullName: String { get }
}
- FullyNamed 프로토콜은 완전한 이름을 제공하기 위해 적합한 타입이 필요하다.
- 프로토콜은 적합한 타입의 특성에 대해 다른 어떤 것도 지정하지 않는다.
- 단지 타입이 자체적으로 전체 이름을 제공할 수 있어야 한다는 것만 지정한다.
- 프로토콜은 모든 타입에 fullname 이라는 string 형식의 gettable 인스턴스 프로퍼티가 있어야 한다고 명시되어있다.
프로토콜을 채택하고 사용하는 간단한 구조체의 예시
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
- 여기 예시는 특정한 사람의 이름을 나타내는 Person이라는 구조체가 정의되었다.
- 첫번째줄에 FullyNamed 프로토콜을 채택한다고 명시되어있다.
- Person의 각 인스턴스는 fullName이라는 String 타입의 단일 저장 프로퍼티가 있다.
- 이는 프로토콜의 단일 요구사항과 일치하며, 프로토콜의 요구사항을 준수하고있음을 의미한다.( 준수하고있지 않다면 컴파일 타임에 에러 보고)
좀더 복잡한 예시
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"
- 이 클래스는 fullName 프로퍼티를 Starship에 대한 계산된 읽기 전용 프로퍼티로 프로토콜의 요구사항을 구현했다.
- 각 클래스 인스턴스는 필수인 name과 옵셔널 prefix를 저장한다.
- fullName 프로퍼티는 prefix 값이 존재하면 사용하고, name 앞에 추가한다.
Method Requirements
- 프로토콜은 특정 인스턴스 메소드와 타입을 준수하여 구현되는 타입 메소드를 요구할 수 있다.
- 이러한 메소드는 일반 인스턴스 및 타입 메소드와 정확히 동일한 방식으로 프로토콜 정의의 일부로 작성되지만, 중괄호나 메소드 본문은 없다.
- 일반 방법과 동일한 규칙에 따라 가변 매개변수가 허용된다.
- 그러나 기본값은 프로토콜 정의 안에서 메소드 파라미터로 지정될 수 없다.
- 타입 프로퍼티와 마찬가지로 메소드 요구사항에도 접두사 static이 붙을 수 있다.
protocol SomeProtocol {
static func someTypeMethod()
}
- 다음 예제는 단일 인스턴스 메소드 요구사항이 있는 프로토콜을 정의
protocol RandomNumberGenerator {
func random() -> Double
}
- RandomNumberGenerator 프로토콜은 호출될때마다 Double 값을 리턴하는 Random 인스턴스 메소드를 요구한다.
- 프로토콜의 일부로 지정되지 않았지만, 이 값은 최소 0.0 에서 최대 1.0 사이의 숫자로 지정된다.
- RandomNumberGenerator 프로토콜은 각 난수가 어떻게 생성되는지 지정하지 않았다.
- 단지 Random 메소드가 채택된 다른 곳에서 새로운 난수를 생성하는 표준 방법을 제공하기만 하면 된다.
RandomNumberGenerator 프로토콜을 채택한 클래스
- 이 클래스는 선형 합동 생성기로 일려진 난수 생성기를 구현한다.
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c)
.truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"
Mutating Method Requirements
- 메소드가 자신이 속한 인스턴스를 수정해야하는 경우가 있다.
- 값 유형(구조체, 열거형)의 경우 mutating 키워드를 func 키워드 앞에 배치하여 해당 메소드가 속한 인스턴스의 모든 프로퍼티가 수정가능함을 알린다.
- 프로토콜을 채택하는 모든 타입의 인스턴스를 수정하려면, 프로토콜 정의 내에서 일부 메소드를 mutating 키워드를 붙여야한다.
- 이를 통해 구조체와 열거형이 프로토콜을 채택하고 해당 메소드의 요구사항을 충족할 수 있다.
- 클래스에 대한 해당 메소드 구현을 작성할때 mutating 키워드를 작성할 필요가 없다. 구조체와 열거형에서만 사용된다.
아래 예시는 toggle 메소드가 정의된 Togglable 프로토콜을 정의한다.
- 이름에서 알다시피 toggle 메소드는 해당 프로퍼티를 수정하여 적합한 상태로 전환하거나 반전시키는 것이다.
- toggle 메소드는 Togglable 프로토콜 정의부분에서 mutating 키워드를 붙였다.
- 메소드가 호출될때 적합한 인스턴스의 상태를 변경할 것으로 예상된다.
protocol Togglable {
mutating func toggle()
}
- 구조체나 열거형에 Togglable 프로토콜을 구현하는 경우, 해당 구조체나 열거형은 mutating으로 표시된 toggle() 메소드를 구현하여 프로토콜을 준수할 수 있다.
아래 예시는 OnOffSwitch 열거형을 정의한다.
- 이 열거형은 두가지 상태 사이를 전환한다. ( case On , case Off )
- 이 열거형의 toggle은 Togglable 프로토콜과 일치하도록 mutating을 표시하여 구현하도록 한다.
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch is now equal to .on