익스텐션 채택 및 준수
- 기존 타입의 소스코드에 접근할 수 없는 경우에도 마찬가지로, 기존 프로토콜 익스텐션(
Extension
) 하여 새 프로토콜을 채택하고 준수할 수 있다. - 익스텐션은 기존 타입에 새로운 프로퍼티 및 메서드, 하위 스크립트를 추가할 수 있기 때문에 프로토콜에서 요구할 수 있는 모든 요구사항을 추가할 수 있다.
protocol Person {
var name: String { get }
}
위 프로토콜은 읽기전용 프로퍼티만 요구하고 있다. 그렇기에 모든 종류의 프로퍼티를 만족시킬 수 있다. 이것을 Extension
을 사용하면 아래와 같다.
protocol Person {
var name: String { get }
}
extension String: Person {
}
String
타입을 익스텐션 시켜주고, Person
을 채택했다. 이제 준수를 해야 할 차례이다.
다시 한번 말하지만 읽기 전용(getter
) 이므로 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있다.
extension String: Person {
var name: String {
return "안녕하세요 \(self) 입니다!" // self = String을 의미
}
}
이렇게 연산프로퍼티로 정의해 줬는데, 주의할 점은 extension 안에는 저장 프로퍼티로 만들면 안 된다!
extension String: Person {
var name: String // 저장 프로퍼티 이므로 오류!
}
이제 이 익스텐션은 다음과 같이 호출 가능하다.
protocol Person {
var name: String { get }
}
extension String: Person {
var name: String {
return "안녕하세요 \(self) 입니다!"
}
}
var myName: String = "서근"
print(myName.name) //안녕하세요 서근 입니다!
myName
이 String
타입으로 extenstion
의 요구를 충족해서 성공적으로 호출 가능한 것!
또 다른 예제를 들어보자면..
protocol calculator {
var returnDouble: Double { get }
mutating func add(number: Int)
}
extension Int: calculator {
var returnDouble: Double {
//Double타입을 'self'즉, Int 타입으로 반환
return Double(self)
}
mutating func add(number: Int) {
self += number
}
}
var number = 25
number.add(number: 20) //number은 25이기 때문에 25 + 20 = 45
number.returnDouble //45
calculator
프로토콜에 returnDouble
읽기 전용 프로퍼티와, mutating
함수가 존재한다.
그리고 Int
타입을 익스텐션 후, calculator
를 채택했다. 이제 준수 고고!
returnDouble
을 연산 타입으로 정의해줘야 하기에 return Double(self)
즉, Double
타입을 익스텐션 타입인 Int
로 리턴 해준다는 의미!
그리고 Int
는 값 타입 이기 때문에 값이 변경될 때는 반드시 mutating
키워드를 사용해야 함! 그래서 이 add
메서드는 self
에 파라미터로 전달받은 값을 더하는 코드가 된다.
이것을 호출하면 var number = 25
로써 number.returnDouble
이 (25)
가 됐고, add
파라미터로 (20)
을 줬으니, 25 + 20
이 됨! 결과적으로 number.returnDouble
은 (45)
가 된다!
프로토콜 타입 컬렉션
이 전 게시글에서 타입으로서의 프로토콜을 봤는데, 컬렉션(배열, 세트, 딕셔너리, 튜플)에 저장될 타입으로 프로토콜을 사용할 수 있다.
var someArr = [calculator]() //calculator protocol
//extension의 Int가 calculator을 채택하고 준수했기 때문에,
//Int(append(0)) 추가 가능
someArr.append(0) //[0]
someArr.append(1) //[0, 1]
이렇게 배열 또는 딕셔너리를 만들고, 아래와 같이 배열 안에 추가할 수 있다.
그리고, 컬렉션에 저장될 수 있다는 의미는 for
문을 보면 이해할 수 있다.
var someArr = [calculator]()
someArr.append(0) //[0]
someArr.append(1) //[0, 1]
someArr.append(5)
for index in someArr {
print(index.returnDouble)
}
//0.0
//1.0
//5.0
프로토콜의 상속과 클래스 전용 프로토콜(Inheritance)
프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다.
프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 여러 개의 상속된 프로토콜을 쉼표로 구분하여 나열하는 옵션이 존재한다.
protocol calculator {
var returnDouble: Double { get }
mutating func add(number: Int)
}
protocol InheritingProtocol: calculator {
mutating func remove(number: Int)
}
struct Seogun: InheritingProtocol {
var myDouble: Double = 20.0
var returnDouble: Double {
return myDouble
}
mutating func add(number: Int) {
myDouble += Double(number)
}
mutating func remove(number: Int) {
myDouble -= Double(number)
}
}
var seons: Seogun = Seogun()
seons.add(number: 10) // 20 + 10 = 30
seons.remove(number: 12) // 30 - 12 = 18
calculator
프로토콜 외에 InheritingProtocol
가 calculator
를 상속받았기 때문에 calculator
의 요구사항을 구현해줄 필요가 없다.
그리고 상속된 요구사항에 요구사항을 추가할 수 있으니까! mutating func remove(number: Int)
요구사항을 추가해줬다.
그다음으론 Seogun
이라는 구조체를 만들어 InheritingProtocol
을 채택해서 위 두 개의 프로토콜 요구사항을 모두 구현해준 것이다.
또 다른 간단한 예제를 보자!
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
ReadSpeakable
프로토콜은 Readable
프로토콜을 상속받았고, ReadWriteSpeakable
프로토콜은 Readable
과 Writeable
프로토콜을 상속받았다.
class SomeClass: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
SomeClass
는 ReadWriteSpeakable
을 채택 했기때문에 세 프로토콜이 요구하는 read(
), write()
, speak()
메서드를 모두 구현해야만 한다.
만약 SomeClass
가 ReadSpeakable
을 채택했다면? Readable
프로토콜의 read()
메서드와 ReadSpeakable
프로토콜의 speak()
메서드를 구현해주면 된다!
Class Only 프로토콜
프로토콜의 상속 리스트에 class
키워드를 추가해서 프로토콜이 클래스 타입에만 채택될 수 있도록 제한할 수도 있다. 클래스 전용 프로토콜로 제한하기 위해서는 프로토콜의 상속 리스트의 맨 처음에 class
키워드를 위치시키면 된다.
//class 타입에만 채택 가능
protocol ReadWriteSpeakable: class, Readable, Writeable {
//추가 요구사항
}
class SomeClass: ReadWriteSpeakable {
func read() {}
func write() {}
func speak() {}
}
// 오류! ReadWriteSpeakable 프로토콜은 클래스 타입에서만 채택 가능!
struct SomeStruct: ReadWriteSpeakable {
func read() {}
func write() {}
func speak() {}
}
Extension 프로토콜
프로토콜을 사용하면 어떤 메서드가 있어야 하는지 설명할 수 있지만 내부에 코드를 제공하지는 않는다.
Extension
을 사용하면 메서드 내부에 코드를 제공할 수 있지만, 하나의 데이터 형식에만 영향을 준다. 동시에 많은 형식에 메서드를 추가할 수 없다.
프로토콜 익스텐션은 이 두 가지 문제를 모두 해결한다.
예를 들어 다음은 일부 이름을 포함하는 배열과 집합이다.
let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])
Swift의 Array
와 Set
는 모두 Collection
이라는 프로토콜을 준수하므로, 우리는 그 프로토콜에 확장자를 작성하여 컬렉션을 깔끔하게 출력하는 summarize()
메서드를 추가할 수 있다.
extension Collection {
func summarize() {
print("There are \(count) of us:")
for name in self {
print(name)
}
}
}
이제 Array
와 Set
모두에 이 방법이 적용되므로 아래 코드로 호출할 수 있다.
pythons.summarize()
beatles.summarize()
/*
There are 6 of us:
Eric
Graham
John
Michael
Terry
Terry
There are 4 of us:
John
Ringo
Paul
George
*/
다음 게시글은 프로토콜 마지막 포스팅으로 찾아뵙겠습니다 :)
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 기초문법 [익스텐션 - Extensions] (0) | 2022.02.03 |
---|---|
Swift : 기초문법 [프로토콜#5 - 준수, 옵셔널 프로토콜 요구사항] (0) | 2022.01.31 |
Swift : 기초문법 [프로토콜#3 - 타입으로서의 프로토콜, Delegate] (0) | 2022.01.28 |
Swift : 기초문법 [프로토콜#2 - 메서드 요구사항] (0) | 2022.01.27 |
Swift : 기초문법 [프로토콜#1 - 프로퍼티 요구사항] (1) | 2022.01.25 |