2020. 10. 30. 16:25ㆍSwift
상속( inheritance ) 위임( delegate )
프로토콜( Protocol )은 특정 역할을 수행하기 위한 메서드 , 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다.
구조체, 클래스, 열거형 프로토콜은 채택( Adopted ) 해서 특정 기능을 수행하기 위한 프로토코 요구사항을 실제로 구현할 수 있습니다.
어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 '프로토콜을 준수한다( Conform )'고 표현합니다.
타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 합니다.
즉, 프로토콜은 기능을 정의하고 제시할 뿐이지 스스로 기능을 구현하지는 않습니다.
프로토콜 정의
프로토콜은 구조체, 클래스, 열거형의 모양과 비슷하게 정의할 수 있으며 protocol 키워드를 사용합니다.
protocol 프로토콜 이름 {
//프로토콜 정의
}
구조체, 클래스, 열거형 등에서 프로토콜을 채택하려면 타입 이름 뒤에 콜론(:)을 붙여준 후 채택할 프로토콜 이름을 쉼표(,)로 구분하여 명시해 줍니다.
struct SomStruct: AProtocol, AnotherProtocol {
//구조체 정의
}
class SomeClass: AProtocol, AnotherProtocol {
// 클래스 정의
}
enum SomeEnum: AProtocol, AnotherProtocol {
// 열거형 정의
}
위 코드의 각 타입은 AProtocol과 AnotherProtocol을 채택한 것입니다. 만약, 클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 당음에 채택할 프로토콜을 나열해 줍니다.
class SomeClass: SuperClass, AProtocol, AnotherProtocol {
// 클래스 정의
}
위의 SomeClass는 SuperClass를 상속받았으며 동시에 AProtocol과 AnotherProtocol 프로토콜을 채택한 클래스 입니다.
프로토콜 요구사항
프로토콜은 타입이 특정 기능을 수행하기 위해 필요한 기능을 요구합니다.
프로토콜이 자신을 채택한 타입에 요구하는 사항은 프로퍼티나 메서드와 같은 기능들입니다.
프로토콜은 프로퍼티, 메서드, 서브스크립트, 이니셜라이져 등의 기능을 요구할 수 있습니다.
프로퍼티 요구
프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있습니다.
그렇지만 프로토콜은 그 프로퍼티의 종류( 연산 프로퍼티인지, 저장 프로퍼티인지 등 )는 따로 신경쓰지 않습니다.
프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티 이름과 타입만 맞도록 구현해 주면 됩니다.
다만, 프로퍼티를 읽기 전용으로 할 지 혹은 읽고 쓰기가 모두 가능하게 할지는 프로토콜이 정해야합니다.
프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의됩니다.
읽기와 쓰기가 모두 가능한 프로퍼티는 프로퍼티의 정의 뒤에 { get , set }이라고 명시하며,
읽기 전용 프로퍼티는 정의 뒤에 { get }이라고 명시해줍니다.
protocol SomeProtocol {
var settableProperty: String { get set }//읽기와 쓰기 모두 가능
var notNeedToBoSettableProperty: String { get }//읽기 전용
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
static var anotherTypeProperty: Int { get }
}
위 코드의 SomeProtocol에 정의된 settableProperty는 읽기와 쓰기 모두를 요구했고,
notNeedToBeSettableProperty는 읽기만 가능하다면 어떻게 구현되어도 상관없다는 요구사항 입니다.
타입 프로퍼티를 요구하려면 static 키워드를 사용합니다.
클래스의 타입 프로퍼티에는 상속 가능한 타입 프로퍼티인 class타입 프로퍼티와 상속 불가능한 static타입 프로퍼티가 있습니다만 이 두 타입 프로퍼티를 따로 구분하지 않고 모두 static키워드를 사용하여 타입 프로퍼티를 요구하면 됩니다.
AnotherProtocol에 정의된 someProperty와 anotherProperty는 모두 타입 프로퍼티를 요구합니다.
protocol Talkable {
var topic: String { get set }
func talk(to: Person)
}
struct Person: Talkable {
var topic: String
var name: String
func talk(to: Person) {
print("\(topic)에 대해 \(to.name)에게 이야기합니다.")
}
}
이니셜라이저 요구
프로토콜은 프로퍼티, 메서드 등과 마찬가지로 특정한 이니셜라이저를 요구할 수도 있습니다.
프로토콜에서 이니셜라이저를 요구하려면 메서드 요구와 마찬가지로 이니셜라이저를 정의하지만 구현은하지 않습니다.
즉, 이니셜라이저의 매개변수를 지정하기만 할 뿐, 중괄호를 포함한 이니셜라이저는 구현은 하지 않습니다.
protocol Talkable {
var topic: String { get set }
func talk(to: Person)
init(name: String, topic: String)
}
sturuct Person: Talkable {
var topic: String
var name: String
func talk(to: Person) {
print("\(topic)에 대해 \(to.name)에게 이야기합니다.")
}
init(name: String, topic: String) {
self.name = name
self.topic = topic
}
}
let he: Person = Person(name: "he", topic: "영화")
let yj: Person = Person(name: "yj", topic: "주식")
he.talk(to: yj)
//영화에 대해 yj에게 이야기합니다.
프로토콜의 상속
프로토콜은 하나 이상의 푸로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다.
프로토콜 상속 문법은 클래스의 상속 문법과 유사합니다.
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
class SomeClass: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
위 코드의 ReadSpeakable 프로토콜은 Readable프로토콜을 상속받았고,
ReadWriteSpeakable은 Readable과 Writeable프로토콜을 상속받았습니다.
그래서 ReadWriteSpeakable 프로토콜을 채택한 SomeClasss는 세 프로토콜이 요구하는 read(), write(), speak() 메서드를 모두 구현해야 합니다.
extension
extension은 스위프트의 강력한 기능 중 하나입니다.
extension은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능입니다.
기능을 추가하려는 타입의 구현된 소스코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있습니다.
스위프트의 extension이 타입에 추가할 수 있는 기능은 다음과 같습니다.
- 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
- 타입 메서드 / 인스턴스 메서드
- 이니셜라이저
- 서브스크립트
- 중첩 타입
- 특정 프로토콜을 준수할 수 있도록 기능 추가
extension은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없습니다.
클래스의 상속과 extension을 비교해 보겠습니다.
이 둘은 비슷해 보이지만 실제 성격은 많이 다릅니다.
클래스의 상속은 클래스 타입에서만 가능하지만 익스텐션은 구조체, 클래스, 프로토콜 등에 적용이 가능합니다.
또, 클래스의 상속은 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가 기능을 구현하는 수직 확장 이지만, 익스텐션은 기존의 타입에 기능을 추가하는 수평확장입니다.
또, 상속을 받으면 기존 기능을 재정의 할 수 있지만, 익스텐션은 재정의 할 수 없다는 것도 큰 차이 중 하나입니다.
상황과 용도에 맞게 상속과 익스텐션을 선택하여 사용하면 됩니다.
상속 | 익스텐션( extension ) | |
확장 | 수직확장 | 수평확장 |
사용 | 클래스 타입에만 사용 | class, struct, protocol, Generic...etc |
재정의 | 재정의 가능 | 재정의 불가 |
익스텐션을 사용하는 대신 원래 타입을 정의한 소스에 기능을 추가하는 방법도 있겠지만, 외부 라이브러리나 프레임워크를 가져다 썼다면 원본 소스를 수정하지 못합니다.
이처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다.
따로 상속을 받지 않아도 외며, 구조체와 열거형에도 기능을 추가할 수 있으므로 익스텐션은 매우 편리한 기능입니다.
익스텐션은 모든 타입에 적용할 수 있습니다.
모든 타입이라함은 구조체, 열거형, 클래스, 프로토콜, 제너릭 타입 등을 뜻합니다.
즉, 익스텐션을 통해 모든 타입에 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트. 중첩 데이터 타입 등을 추가할 수 있습니다.
더불어 익스텐션은 프로토콜과 함께 사용하면 굉장히 강력한 기능을 선사합니다.
익스텐션 문법
익스텐션은 extension이라는 키워드를 사용하여 선언합니다.
extension 확장할 타입 이름 {
//타입에 추가될 새로운 기능 구현
}
익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다.
이런 경우에는 클래스나 구조체에서 사용하던 것과 똑같은 방법으로 프로토콜 이름을 나열해 줍니다.
extension 확장할 타입 이름: Protocol1, Protocol2, Protocol3 {
//프로토콜 요구사항 구현
}
스위프트 라이브러리를 살펴보면 실제로 익스텐션이 굉장히 많이 사용되고 있음을 알 수 있습니다.
Double타입에는 수많은 프로퍼티와 메서드, 이니셜라이저가 정의되어 있으며 수많은 프로토콜을 채택하고 있을 것이라고 예상되지만, 실제로 Double타입의 정의를 살펴보면 그 모든것이 다 정의되어 있지는 않습니다.
Double외에도 다른 타입들의 정의와 익스텐션을 찾아보면 더 많은 예를 보실 수 있습니다. 찾아보세요 !
익스텐션으로 확장할 수 있는 항목
익스텐션을 통해 추가할 수 있는 기능에는 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트, 중첩 데이터 타입 등이 있습니다.
연산프로퍼티 추가
extension Int {
var isEven: Bool {
return self % 2 == 0
}
var isOdd: Bool {
return self % 2 == 1
}
}
print(1.isEven) //false
print(1.isOdd) //true
print(2.isEven) // true
print(2.isOdd) //false
var num: Int = 3
print(num.isEven) //false
print(num.isOdd) //true
위 코드의 익스텐션은 Int타입에 두 개의 연산 프로퍼티를 추가한 것입니다.
Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool 타입으로 알려주는 연산프로퍼티입니다.
익스텐션으로 Int타입에 추가해준 연산 프로퍼티는 Int 타입의 어떤 인스턴스에도 사용이 가능합니다.
위의 코드처럼 인스턴스 연산 프로퍼티를 추가할 수도 있으며, static 키워드를 사용하여 타입 연산 프로퍼티도 추가할 수도 있습니다.
메서드 추가
extension Int {
func multiply(by n: Int) -> Int {
return self * n
}
}
print(3.multiply(by: 3)) //9
var num: Int = 4
print(num.multiply(by: 2)) //8
위 코드의 익스텐션을 통해 Int타입에 인스턴스 메서드인 multiply(by: ) 메서드를 추가했스비낟.
여러 기능을 여러 익스텐션 블록으로 나눠서 구현해도 전혀 문제가 없습니다.
관련된 기능별로 하나의 익스텐션 블록에 묶어주는것도 좋습니다.
이니셜라이저 추가
인스턴스를 초기화( 이니셜라이즈 ) 할 때 인스턴스 초기화에 필요한 다양한 데이터를 전달받을 수 있도록 여러 종류의 이니셜라이즈를 만들 수 있습니다.
타입의 정의부에 이니셜라이저를 추가하지 않아도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다.
익스텐션으로 클래스 타입에 편의 이니셜라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없습니다.
지정 이니셜라이저와 디이니셜라이저는 만드시 클래스 타입의 구현부에 위치해야 합니다.
extension String {
subscript(appendValue: String) -> String {
return self + appendValue
}
subscript(repeatCount: UInt) -> String {
var str: String = ""
for _ in 0..<repeatCount {
str += self
}
return str
}
}
print("abc"["def"]) // "abcdef"
print("abc"[2])// abcabc
출처 : blog.yagom.net/529/
'Swift' 카테고리의 다른 글
[ Swift ] Grammar (0) | 2020.11.04 |
---|---|
[ Swift ] font 바꾸기 (0) | 2020.11.04 |
[ Swift ] 단축키 (0) | 2020.10.27 |
[ Swift ] DispatchQueue (0) | 2020.10.21 |
[ Swift ] http data 가져오기 data trimming (0) | 2020.10.15 |