익스텐션
- 클래스, 구조체, 열거형, 프로토콜 타입 등에 새로운 기능을 추가하여 확장할 수 있는 기능
- 기능을 추가하려는 타입을 구현한 소스 코드를 알지 못하거나 볼 수 없어도, 타입만 알면 기능 확장 가능
- 익스텐션은 타입에 새로운 기능을 추가할 수 있지만, 기존에 존재하는 기능을 재정의(
override
) 불가 - 소유하지 않은 유형 (예 : Apple 또는 다른 사람이 작성한 유형)을 수정하는 데 도움이 됨
- 외부에서 가져온 프레임워크나 라이브러리에 내가 원하는 기능을 추가하고자 할 때 유용
- 상속을 받지 않아도 구조체와 열거형에도 기능 추가 가능 하므로 매우 편리함
- 익스텐션은 모든 타입에 적용할 수 있다.(구조체, 열거형, 클래스, 프로토콜, 제네릭 타입 등)
익스텐션이 타입에 추가할 수 있는 기능
- 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
- 타입 메서드 / 인스턴스 메서드
- 이니셜라이저
- 서브스크립트
- 중첩 타입
- 특정 프로토콜을 준수할 수 있도록 기능 추가
클래스의 상속과 익스텐션 차이
Class
- 클래스의 상속은 클래스 타입에서만 가능. 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가 기능 구현하는 수직 확장Extension
- 구조체, 클래스, 프로토콜 등에 적용 가능. 기존 타입에 기능을 추가하는 수평 확장
상속 | 익스텐션 | |
확장 | 수직 확장 | 수평 확장 |
사용 | 클래스 타입에서만 사용 | 클래스, 구조체, 프로토콜, 제네릭 등 모든 타입에서 사용 |
재정의 | 재정의 가능 | 재정의 불가 |
익스텐션 문법
이 기능을 사용하려면 extension
이라는 키워드를 사용해야 한다.
기본 형태
extension 확장할 타입 이름 {
//기능 구현
}
익스텐션은 기존에 존재하는 타입이 추가로 다른 프로토콜을 채택할 수 있도록 확장 가능한데, 이경우 클래스나 구조체에서 사용하던 것처럼 같은 방법으로 프로토콜 이름을 나열하면 된다.
extension 확장할 타입 이름: 프로토콜 1, 프로토콜 2, 프로토콜 3 {
// 프로토콜 요구사항
}
Swift 표준 라이브러리 타입(String
, Double,
Int
등)의 기능은 대부분 익스텐션으로 구현되어있다.
익스텐션으로 추가할 수 있는 기능
연산 프로퍼티
익스텐션을 통해 타입에 연산 프로퍼티를 추가할 수 있다.
// Int 타입 인스턴스가 짝수인지 홀수인지 판별하는 연산 프로퍼티
extension Int {
var isEven: Bool {
return self % 2 == 0
}
var isOdd: Bool {
return self % 2 == 1
}
}
print(3.isOdd) // true
print(1.isEven) //false
print(1.isOdd) // true
var number: Int = 5
print(number.isEven) // false
print(number.isOdd) // true
numver = 2
print(number.isOdd) // false
print(number.isEven) // true
Int
타입의 익스텐션에 짝수인지 홀수인지 판별하여 true
/ false
를 반환하게 구현을 했다. 호출을 할 때는 print(Int.isOdd)
처럼 사용하면 된다.
또, static
키워드를 사용하여 연산 타입 프로퍼티도 추가 가능하다.
메서드
익스텐션을 통해 타입에 메서드를 추가할 수 있다. 다음 예제는 Int
타입에 repetitions
라는 인스턴스 메서드를 추가한 예제이다.
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
repetitions(task:)
메서드는 () -> Void
타입의 하나의 인자를 받고 파라미터와 반환 값이 없는 함수이다.
3.repetitions {
print("서근 개발노트!")
}
// 서근 개발노트!
// 서근 개발노트!
// 서근 개발노트!
위 함수를 실행하면 함수 안의 task
를 숫자만큼 반복 실행하게 된다.
이니셜라이저
타입의 정의 부분에 이니셜라이저를 추가하지 않아도 익스텐션을 통해 이니셜라이저를 추가할 수 있다.
익스텐션에서 이니셜라이저를 추가할 때의 조건
- 클래스 타입에 편의 이니셜 라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없다.
- 값 타입에서는 편의 및 지정 이니셜 라이저를 추가할 수 있다.
- 지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 한다.(값 타입 상관없음)
extension String {
init(intTypeNumber: Int) {
self = "\(intTypeNumber)"
}
init(doubleTypeNumber: Double) {
self = "\(doubleTypeNumber)"
}
}
let stringFromInt: String = String(intTypeNumber: 20000) // "20000"
let stringFromDouble: String = String(doubleTypeNumber: 3.14159) // "3.14159"
위 예제는 String
타입의 익스텐션을 정의하고 이니셜라이저로 Int
또는 double
타입을 String
으로 변환하는 코드이다.
그리고 클래스 타입에서도 편의 이니셜라이저를 다음과 같이 추가 가능하다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
extension Person {
// Person 클래스의 편의 이니셜라이저
convenience init() {
// Person의 지정 이니셜라이저를 호출하여 매개변수에 "Anonymous" 값 전달
self.init(name: "Anonymous")
}
}
let someOne: Person = Person()
print(someOne.name) // "Anonymous"
익스텐션으로 값 타입에 이니셜라이저를 추가했을때, 해당 값 타입이 아래 조건을 모두 성립하면 익스텐션으로 사용자정의 이니셜라이저를 추가한 이후에도 해당 타입의 기본 이니셜라이저와 멤버와이즈 이니셜라이저 호출 가능
- 그 값 타입의 모든 저장 프로퍼티가 기본값을 가지고 있어야 함
- 타입에 기본 이니셜라이저와 멤버와이즈 이니셜라이저 외에 추가 사용자 정의 이니셜라이저가 없어야 함
익스텐션을 통해 추가하는 이니셜라이저는 타입의 기존 이니셜라이저가 갖는 책임과 의무를 동일하게 수행해야 한다.
struct Rect {
var x: CGFloat = 0
var y: CGFloat = 0
}
extention Rect {
init(width: CGFloat, height: CGFloat) {
//구현
self.init(x: 9, y: 8)
}
}
좀 더 복잡한 예시를 들어보자면
아래 Size
와 Point
구조체는 기본값을 가졌고, 사용자 정의 이니셜라이저를 구현하지 않았기 때문에 기본 이니셜라이저와 멤버와이즈 이니셔랄이저를 사용할 수 있다. 때문에 익스텐션에서 추가해주는 새로운 이니셜라이저는 멤버와이즈 이니셜라이저에게 초기화를 위임할 수 있다.
struct Size {
var width: Double = 0.0
var height: Double = 0.0
}
struct Point {
var x: Double = 0.0
var y: Double = 0.0
}
struct Rect {
var origin: Point = Point()
var size: Size = Size()
}
let defaultRect: Rect = Rect()
let memberwiseRect: Rect = Rect(origin: Point(x: 1.0, y: 2.0),
size: Size(width: 100, height: 200))
extension Rect {
init(center: Point, size: Size) {
let originX: Double = center.x - (size.width / 2)
let originY: Double = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let centerRect: Rect = Rect(center: Point(x: 3.0, y: 3.0),
size: Size(width: 100, height: 200))
잘못된 예시
extension Int {
var isEven {
return self % 2 == 0
}
}
//isEven에 어노테이션 타입이 없다.
extension Bool {
func toggled() -> Bool {
if self = true {
return false
} else {
return true
}
}
}
//if self == true 로 써야 한다.
서브스크립트
익스텐션을 통해 타입에 서브스크립트를 추가할 수 있다.
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("서근"["개발노트"]) // "서근개발노트"
print("서근개발노트"[2]) // "서근개발노트서근개발노트"
또 다른 예제를 보자면..
Swift의 built-in
타입에 integer
서브스크립트를 추가한 예제이다. 서브스크립트 [n]
은 숫자의 오른쪽에서부터 n
번째 위치하는 정수를 반환한다.
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
// 10 * n번째 수로 현재 수를 나눈 것의 나머지
// 1인 경우 746381295 % 10 -> 5가 나머지
// 2인 경우 746381295 % 10 -> 9가 나머지
}
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7
만약 Int
값에서 요청한 값이 처리할 수 있는 자릿수를 넘어가면 서브스크립트 구현에서 0
을 반환한다.
746381295[9]
// 9로 처리할 수 있는 자릿 수를 넘어가면 0을 반환
0746381295[9]
중첩 데이터 타입
익스텐션을 통해 타입에 중첩 데이터 타입을 추가할 수 있다.
아래 예제에 대해 설명을 해보자면
- 익스텐션을 통해
Int
타입에Kind
열거형 타입과Kind
타입의 연산 프로퍼티를 추가 kind
프로퍼티는 인스턴스가 양수인지 음수인지Zero
인지를 판단해Kind
에 반환하는 연산 프로퍼티printIntegerKinds(number:)
함수는Int
타입의 값의 배열을 전달받아서 각 값의 부호를print()
함수를 통해 출력하는 함수print()
함수의 매개변수 중terminator
를 사용한 이유는 줄 바꿈을 하지 않기 위해 기본적으로 줄바꿈 문자로 지정되어 있는terminator
에 빈 문자열""
을 전달해준 것이다.
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
print(1.kind) //positive
print(0.kind) //zero
print((-1).kind) //negative
func printIntegerKinds(numbers: [Int]) {
for number in numbers {
switch number.kind {
case .negative:
print("- ", terminator: "")
case .zero:
print("0 ", terminator: "")
case .positive:
print("+ ", terminator: "")
}
}
print("")
}
printIntegerKinds(numbers: [3, 20, -2, -31, 0, 2])
//+ + - - 0 +
.number.kind
가 이미 switch
에서 Int.Kind
타입이라는 것을 알고 있기 때문에 안의 case
에서 kind
의 축약형인 .negative
, .zeo
, .positive
로 사용할 수 있다.
읽어주셔서 감사합니다🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 고급 문법 [패턴 - Patterns] (0) | 2022.02.05 |
---|---|
Swift : 기초문법 [제네릭 - Generic] (0) | 2022.02.03 |
Swift : 기초문법 [프로토콜#5 - 준수, 옵셔널 프로토콜 요구사항] (0) | 2022.01.31 |
Swift : 기초문법 [프로토콜#4 - 익스텐션, 컬렉션 타입, 상속] (0) | 2022.01.30 |
Swift : 기초문법 [프로토콜#3 - 타입으로서의 프로토콜, Delegate] (0) | 2022.01.28 |