Extensions
- 익스텐션을 이용해 클래스, 구조체, 열거형 혹은 프로토콜 타입에 기능을 추가할 수 있습니다. retroactive modeling으로 알려진 것과 같이 원본 코드를 몰라도 그 타입에 대한 기능을 확장할 수 있습니다. 익스텐션은 Objective-C의 카테고리와 유사합니다. Swift에서 익스텐션을 이용해 다음을 할 수 있습니다.
* 계산된 인스턴스 프로퍼티와 계산된 타입 프로퍼티의 추가
* 인스턴스 메소드와 타입 메서드의 추가
* 새로운 이니셜라이저 제공
* 서브스크립트 정의
* 중첩 타입의 선언과 사용
* 특정 프로토콜을 따르는 타입 만들기
- 익스텐션은 타입에 새 기능을 추가할 수 있지만 오버라이드는(override)는 할 수 없습니다.
Extension Syntax
- 익스텐션은 extension 키워드를 사용해 선언합니다.
extension SomeType {
// new functionality to add to SomeType goes here
}
- 하나의 익스텐션에서 현재 존재하는 타입에 한 개 이상의 프로토콜을 따르도록 확장할 수 있습니다.
extension SomeType: SomeProtocol, AnotherProtocol {
// implementation of protocol requirements goes here
}
- 이런 방식으로 프로토콜을 구현하는 것은 Addind Protocol Conformance with an Extension에 설명되어 있습니다.
- 하나의 익스텐션은 Extending a Generic Type에 설명되어 있는 것처럼 generic 타입으로 확장하는 데 사용할 수 있습니다.
- generic 타입에 조건적으로 기능을 추가 할 수 있는 것은 Extensions with a Generic Where clause에 설명되어 있습니다.
- 익스텐션을 정의하여 존재하는 타입에 새 기능을 추가하면, 그 기능은 익스텐션을 정의하기 이전에 생성한 인스턴스를 포함한 존재하는 모든 해당 타입의 인스턴스에서 사용 가능합니다.
Computed Properties
- 익스텐션을 이용해 존재하는 타입에 Computed Properties와 type properties를 추가할 수 있습니다. 다음 예제는 Swift의 built-in 타입인 Double에 5개의 Computed Properties를 추가하는 예제입니다.
extension Double {
var km: Double { return self 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// Prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// Prints "Three feet is 0.914399970739201 meters"
- 주어진 Double값에 km, m, cm 등 단위를 붙여 미터로 변경하는 계산된 프로퍼티입니다.
- 단위 변환은 m(미터)를 기준으로 합니다.
- 이 프로퍼티들은 read-only Computed Properties 이기 때문에 간결함을 위해 get을 생략합니다.
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"
- 단위 변환 값이 Double 타입이기 때문에 각각의 변환 값의 연산도 가능합니다.
- 익스텐션은 새 계산된 값을 추가할 수 있지만 새로운 저장된 프로퍼티나 프로퍼티 옵서버를 추가할 수는 없습니다.
Initializers
- 익스텐션을 이용해 존재하는 타입에 새로운 이니셜 라이저를 추가할 수 있습니다. 이 방법으로 커스텀 타입의 이니셜라이저 파라미터를 넣을 수 있도록 변경하거나 원래 구현에서 포함하지 않는 초기화 정보를 추가할 수 있습니다.
- 익스텐션은 클래스에 새로운 편리한 이니셜 라이저(convenience initializer)를 추가할 수는 있지만 지정된 이니셜 라이저(designated initializers)나 디이니셜 라이저(deinitializers)를 추가할 수는 없습니다. 지정된 이니셜 라이저는 항상 반드시 오리지널 클래스의 구현에서 작성돼야 합니다.
- 만약 익스텐션을 값 타입에 이니셜 라이저를 추가하는데 사용하고, 그 값타입이 모든 프로퍼티에 대해 기본 값을 제공하고 커스텀 이니셜라이저를 정의하지 않았다면, 익스텐션에서 기본 이니셜라이저와 멤버쪽 이니셜라이저를 익스텐션에서 호출할 수 있습니다. 만약 값 타입의 오리지널 구현의 부분으로 이니셜라이저 코드를 작성했다면 그것은 이 경우에 해당하지 않습니다.
- 만약 다른 모듈에 선언돼 있는 구조체에 이니셜라이저를 추가하는 익스텐션을 사용한다면 새로운 이니셜 라이져는 모듈에 정의된 이니셜라이저를 호출하기 전까지 self에 접근할 수 없습니다.
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
- Size와 Point구조체를 정의하고 그것을 사용하는 Rect 구조체를 정의했습니다.
- Rect 구조체에서 모든 프로퍼티의 기본 값을 제공하기 때문에 Rect구조체는 기본 이니셜라이저와 멤버쪽 이니셜 라이저를 자동으로 제공 받아 사용할 수 있습니다.
- 이니셜라이저를 사용해 초기화를 한 예제입니다.
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
- Rect 구조체를 추가적인 이니셜 라이저를 제공하기 위해 확장 할 수 있습니다.
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
- Rect에서 확장한 이니셜라이저를 사용한 코드는 다음과 같이 사용할 수 있습니다.
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
- 익스텐션에서 이니셜라이저를 제공할 때 각 인스턴스가 이니셜 라이저가 한번 완료되면 완전히 초기화되도록 확실히 해야 합니다.
Methods
- 익스텐션을 이용해 존재하는 타입에 인스턴스 메서드나 타입 메서드를 추가할 수 있습니다. Int 타입에 repetitions라는 인스턴스 메소드를 추가한 예제입니다.
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
- repetitions(task:) 메서드는 () -> Void 타입의 하나의 인자를 받고 파라미터와 반환 값이 없는 함수입니다.
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
- 함수를 실행하면 함수 안의 task를 숫자만큼 반복 실행합니다.
Mutating Instance Methods
- 익스텐션에서 추가된 인스턴스 메서드는 인스턴스 자신(self)을 변경할 수 있습니다.
- 구조체와 열거형 메소드 중 자기 자신(self)을 변경하는 인스턴스 메서드는 원본 구현의 mutating 메서드와 같이 반드시 mutating으로 선언돼야 합니다.
- mutating 메서드를 추가하고 호출하는 예제입니다.
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt is now 9
Subscripts
- 익스텐션을 이용해 존재하는 타입에 새로운 서브 스크립트를 추가할 수 있습니다. 다음 예제는 Swift의 built-in 타입에 integer 서브스크립트를 추가한 예제입니다. 서브스크립트 [n]은 숫자의 오른쪽에서부터 n번째 위치하는 정수를 반환합니다.
* 123456789 [0] returns 9
* 123456789 [1] returns 8
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7
- 만약 Int값에서 요청한 값이 처리할 수 있는 자릿수를 넘어가면 서브 스크립트 구현에서 0을 반환합니다.
746381295[9]
// returns 0, as if you had requested:
0746381295[9]
Nested Types
- 익스텐션을 이용해 존재하는 클래스, 구조체, 열거형에 중첩 타입을 추가할 수 있습니다.
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
}
}
}
- 위 예제는 Int에 중첩형 enum을 추가한 예제입니다. Kind라고 불리는 열거형은 Int를 음수, 0, 양수로 표현합니다.
- 아래 예제는 새로운 계산된 프로퍼티 kind를 이용해 특정 수가 음수, 0, 양수 중 어떤 것인지를 나타내는 예제입니다.
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([3, 19, -27, 0, -6, 0, 7])
// Prints "+ + - 0 - 0 + "
- printIntegerKinds(_:) 함수를 Int 배열을 입력으로 받아 각 Int가 음수, 0, 양수 어디에 속하는지 계산해서 그에 맞는 기호를 반환하는 함수입니다.
- number.kind가 이미 switch에서 Int.Kind 타입이라는 것을 알고 있기 때문에 안의 case에서 kind의 축약형인. negative,. zeo,. positive로 사용할 수 있습니다.
'컴퓨터 언어 > Swift' 카테고리의 다른 글
(Swift) 18. Nested Types (중첩 타입) (0) | 2021.12.17 |
---|---|
(Swift) 17. Type Casting (타입 캐스팅) (0) | 2021.12.17 |
(Swift) 16 . Error Handling (에러 처리) (0) | 2021.12.15 |
(Swift) 15. Optional Chaining (옵셔널 체이닝) (0) | 2021.12.15 |
(Swift) 14. Deinitialization (초기화 해지) (0) | 2021.12.15 |