BOID
[swift] 확장(extensions)이란? 본문
안녕하세요 HoonIOS입니다 :)
이번은 swift의 extensions에 대해 공부를 해봤는데요, 제가 생각한 것보다 훨씬 포괄적이고 제가 모르는 부분이 많아서 포스팅을 했습니다. 그럼 한번 알아보겠습니다.
extensions 란?
* 애플 공식 문서에서 정의된 extensions
- 공식문서에서 보면 새로운 함수적 기능을 class, structure, enumeration, or protocol type에 확장한다는 의미를 지니고 있습니다.
- 익스텐션은 스위프트의 강력한 기능 중 하나로 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있다.
- 스위프트의 익스텐션이 타입에 추가할 수 있는 기능은 다음과 같다.
- 연산 타입 프로퍼티/ 연산 인스턴스 프로퍼티
- 타입 메서드/ 인스턴스 메서드
- 이니셜라이저
- 서브스크립트
- 중첩 타입
- 특정 프로토콜을 준수할 수 있도록 기능 추가
- 익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존 새로운 기능을 재정의(override) 할수는 없다.
- 클래스의 상속과 익스텐션을 비교해 보겠습니다.
상속 | 확장 | |
타입 | 클래스 타입에서만 가능하다 | 구조체, 클래스, 프로토콜 등에 적용이 가능하다. |
특징 | 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가기능을 구현하는 수직 확장이다. | 기존 타입에 기능을 추가하는 수평 확장이다. |
재정의(override) | 기존 기능을 재정의 할수 있다. | 재정의가 불가능 하다. |
- 익스텐션을 쓰는 이유는 외부 라이브러리나 프레임워크를 가져다 사용했다면 원본 소스를 수정하지 못한다. 이 처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다.
익스텐션 문법
- 문법
extension 확장할 타입이름 {
//타입에 추가될 새로운 기능 구현
}
-익스텐션은 기존에 존재하는 타입이 추가로 다른 프로토콜을 채택할 수 있도록 확장할 수 있는데, 이런 경우는 다음과 같이 구분해준다
- 프로토콜을 채택할 경우의 문법
extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3 {
//프로토콜 요구사항 구현
}
익스텐션으로 추가할 수 있는 기능
연산 프로퍼티
- 익스텐션을 통해 타입에 연산 프로퍼티를 추가할 수 있습니다.
- 예시)
extension Int {
var isEven: Bool { //짝수일때 true를 반환
return self % 2 == 0
}
var isOdd: Bool { //홀수일때 true를 반환
return self % 2 == 1
}
- 위 코드의 익스텐션은 Int타입에 두 개의 연산 프로퍼티를 추가한 것이다. 한 개는 짝수인지를 판별하는 Bool타입 연산 프로퍼티고 다른 하나는 홀수인지를 판별하는 Bool타입 연산 프로퍼티입니다.
- 위 익스텐션으로 Int타입에 추가해준 연산 프로퍼티는 int타입의 어떤 인스턴스에서도 사용 가능합니다.
- Static키워드를 사용하면 타입 연산 프로퍼티도 추가를 할 수 있습니다.
*여기서 타입 연산 프로퍼티는 타입 프로퍼티는 모든 타입이 사용할 수 있는 상수 프로퍼티라고 합니다.
- 익스텐션으로 연산 프로퍼티는 추가할 수는 있지만, 저장 프로퍼티는 추가할수가 없습니다. 또, 타입에 정의되어있는 기존의 프로퍼티에 프로퍼티 감시자를 추가할수 없습니다.
메서드
- 익스텐션을 통해 타입 메서드를 추가할수 있습니다.
- 예시)
extension Int {
func multiply(by n: Int) -> Int {
return self * n
}
mutating func multyplySelf(by n: Int) {
self = self.multiply(by: n)
}
static func isIntTypeInstance(_ instance: Any) -> Bool { //타입이 int인지 확인하는 Bool타입, 타입메서드이다.
return instance is Int
}
}
- 위 메서드는 Int타입에 여러 종류의 메서드를 추가해 주었습니다.
이니셜라이저
- 인스턴스를 초기화할 때는 여러 종류의 이니셜 라이저를 만들 수 있습니다.
- 타입의 정의 부분에 이니셜라이저를 추가하지 않더라도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다.
- 단, 익스텐션으로 클래스 타입에 편의 이니셜라이저를 추가 할수는 있지만, 지정 이니셜라이저는 추가를 해줄수 없습니다! (헷갈리지 않는게 중요합니다!)
- 익스텐션에 지정 이니셜라이저를 추가해줄수 없는 이유는 지정이니셜라이저와 디 이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 하기 때문입니다.
- 익스텐션으로 값 타입(열거형, 구조체 등)에 이니셜라이저를 추가했을때 해당 값 타입이 다음 조건을 모두 성립한다면 익스텐션으로 사용자 정의 이니셜라이저를 추가한 이후에도 해당 타입의 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출할수 있다.
- 모든 저장 프로퍼티에 기본값이 있다.
- 타입에 기본 이니셜라이저와 멤버와이즈 이니셜라이저 외에 추가 사용자 정의 이니셜라이저가 없다.
*멤버와이즈 이니셜라이저란?
- 이니셜라이저를 선언 안 했는데 자동으로 기본 프로퍼티가 포함되어있는 이니셜라이저가 생성되는 것을 멤버 와이즈 이니셜라이저라고 한다.
(이해가 잘 안되시겠지만 다음 예제를 통해서 멤버와이즈 이니셜라이즈가 뭔지 설명해 드리겠습니다..ㅠㅠ) - 익스텐션을 통해 추가하는 이니셜라이저는 타입의 기존 이니셜라이저가 갖는 책무를 동일하게 수행해야 한다.
즉, 이니셜라이저 호출이 종료되는 시점까지 인스턴스가 정상적으로 완벽하게 초기화하는 것을 책임져야 한다.
- 위 조건을 통과하면 이제 익스텐션으로 추가한 이니셜라이저를 해당타입의 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출 할수 있습니다.
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 orgin: Point = Point()
var size: Size = Size()
}
let defaultRect: Rect = Rect()
let memberwiseRect: Rect = Rect(orgin: Point(x:2.0, y:2.0), size: Size(width: 5.0, height:5.0))
//이렇게 이니셜라이저를 따로 설정은 해주지 않았지만 자동으로 이니셜라이저가 되는것을 멤버와이즈 이니셜라이저라고 합니다.
extension Rect {
init(center: Point, size: Size) {
let orginX: Double = center.x - (size.width / 2)
let orginY: Double = center.y - (size.height / 2)
self.init(orgin: Point(x: orginX, y: orginY), size: Size)
}
}
let centerRect: Rect = Rect(center: Point(x:4.0, y:4.0), size: Size(width: 3.0, height: 3.0))
-위 코드에서 memberwiseRect 프로퍼티는 이렇게 이니셜라이저를 따로 설정해 주지 않았는데 자동으로 Size, Point, Rect구조체의 이니셜라이저가 자동으로 생겼는데요 이것을 멤버와이즈 이니셜라이저라고 합니다.
- 위 예시를 보면 Size 구조체와 Point구조체의 모든 저장 프로퍼티는 기본값을 가지면 추가로 사용자 정의 이니셜라이저를 구현하지 않았기 때문에 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 사용할 수 있는 것을 볼 수 있다.
- 이 이유 때문에 익스텐션에서 추가해주는 새로운 이니셜라이저는 멤버와이즈 이니셜라이저에게 초기화를 위임해줄 수도 있습니다.
중첩 데이터 타입
- 익스텐션을 통해 타입에 중첩 데이터 타입을 추가해 줄수 있습니다.
- 예시)
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,19,-27,0,-6,0,7]) //+,+,-,0,-,0,+
- 위의 예시는 익스텐션을 통해 Int타입에 Kind라는 열거형 타입과 Kind타입의 연산 프로퍼티를 추가해 주었습니다.
- Kind프로퍼티는 인스턴스가 양수인지 음수인지 0인지 판별하여 Kind를 반환하는 연산 프로퍼티입니다.
- printIntegerKinds(numbers:) 함수는 Int 타입의 값의 배열을 전달받아 각 값의 부호를 kind연산 프로퍼티를 통해 해당 값을 print()해주는 함수이다.
- 이번 예제를 공부하다가 print 함수 뒤에 terminator 매개변수를 볼 수 있는데 이 매개변수 역할을 찾아보니깐 한 줄로 쭉 출력하기 위한 것으로 줄 바꿈을 하지 않기 위해 기본적으로 줄바꿈 문자로 지정되어있는 terminator에 빈 문자열을 전달해 준 것입니다.
만약 terminator에 빈 문자열로 지정해주지 않으면 출력은
+
+
-
0
-
0
+
로 출력이 되었겠지만 terminator에 빈 문자열로 지정해주고 출력을 하면 아래처럼 numbers에 수만큼 일자로 출력을 하게 될 것입니다.
++-0-0+
지금까지 extension을 공부해봤는데요 이걸 공부하기 전까지는 그냥 확장한다고만 알고 있었는데 이번에 공부하면서 추가할 수 있는 기능까지 공부할 수 있고 다방면으로 extension을 공부하게 되어서 좋았던 포스팅입니다 :)
그럼 오늘도 좋은 하루 보내세요 감사합니다 ^^
'swift 시작기' 카테고리의 다른 글
[swift] 스위프트에서 사용하는 패턴(와일드카드 패턴, 식별자 패턴, 바인딩 패턴, 튜플 패턴) - HoonIOS (3) | 2021.03.26 |
---|---|
[swift] 타입 중첩이란? (0) | 2021.03.23 |
[Swift] 프로토콜 지향 프로그래밍이란? (1) | 2021.03.21 |
[swift] 제네릭이란?(2/2) (0) | 2021.03.18 |
[swift] 제네릭이란?(1/2) (0) | 2021.03.18 |