본문 바로가기
프로그래밍/Swift

[Swift] Extensions

by 별준 2022. 2. 3.

Refences

Contents

  • Extension Syntax
  • Computed Properties
  • Initializers
  • Methods
  • Subscripts
  • Nested Types

Extensions

익스텐션(extension)을 사용하면 이미 정의된 클래스(class), 구조체(structure), 열거형(enumeration), 또는 프로토콜(protocol) 타입에 새로운 기능을 추가할 수 있습니다. 이는 원본 코드를 건들이지 않고 타입을 확장할 수 있는 기능이며, retroactive modeling이라고 합니다. (익스텐션은 Objective-C의 카테고리와 유사하다고 합니다.)

 

익스텐션으로 할 수 있는 것들은 다음과 같습니다.

  • Add compute instance properties and compute type properties
  • Define instance methods and type methods
  • Provide new initializers
  • Define subscripts
  • Define and use new nested types
  • Make an existing type conform to a protocol
익스텐션은 타입에 새로운 기능을 추가할 수는 있지만,  오버라이드(override)는 할 수 없습니다.

Extension Syntax

익스텐션은 extension 키워드를 사용하여 선언할 수 있습니다.

 

extension SomeType {
    // new functionality to add to SomeType goes here
}

익스텐션은 존재하는 타입을 확장하여 하나 이상의 프로토콜을 채택할 수 있습니다. 프로토콜을 추가하려면 클래스와 구조체에 적용하는 것과 동일한 방식으로 프로토콜 이름을 아래와 같이 작성하면 됩니다.

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

(프로토콜에 대한 자세한 내용은 다음 포스팅에서 살펴보겠습니다.)

 

 


Computed Properties

처음 언급한대로 익스텐션을 사용하면 존재하는 타입에 computed instance properties나 compute type properties를 추가할 수 있습니다. 아래 예제는 스위프트에 내장된 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"

추가된 computed properties는 길이에 대한 단위를 고려한 Double 값을 표현합니다. 이들은 computed properties로 구현되지만, 이 속성들의 이름을 부동소수점 리터럴 값 뒤에 dot syntax로 추가해주면 거리 값을 변환할 수 있습니다.

 

이 속성들은 read-only 입니다. 따라서 축약하여 get 키워드없이 표현할 수 있습니다. 이들은 Double 타입의 값을 리턴하며 Double 타입이 사용되는 모든 계산에 사용될 수 있습니다.

let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"

 

익스텐션은 새로운 computed properties는 추가할 수 있지만 store properties나 property observers는 추가할 수 없습니다.

Initializers

익스텐션으로 새로운 이니셜라이저를 추가할 수도 있습니다. 이를 사용하면 타입을 확장하여 이니셜라이저 파라미터로 커스텀 타입을 매개변수로 읽거나 원래 구현에 포함되지 않은 초기화 옵션을 추가할 수 있습니다.

 

익스텐션으로 새로운 convenience initializer는 클래스에 추가할 수 있지만, designated initializer 또는 deinitializer는 추가할 수 없습니다. Designated initializer/deinitializer는 반드시 원본 클래스 구현에서 제공되어야 합니다.

 

만약 익스텐션을 사용하여 모든 저장 속성에 default 값이 설정되어 있고, 어떠한 커스텀 이니셜라이저를 정의하지 않은 value 타입에 이니셜라이저를 추가한다면, 익스텐션 이니셜라이저에서 기본 이니셜라이저와 memberwise 이니셜라이저를 호출할 수 있습니다. value 타입의 원본 구현에 이니셜라이저를 작성한 경우는 해당되지 않습니다.

 

만약 익스텐션을 사용하여 다른 모듈에서 선언된 구조체에 이니셜라이저를 추가한다면, 새로운 이니셜라이저는 모듈에 정의된 이니셜라이저를 호출하기 전까지 self에 액세스할 수 없습니다.

아래 예제 코드는 직사각형을 나타내는 커스텀 Rect 구조체를 정의하고 있습니다. 이 예시에서는 Rect를 서포트하는 Size와 Point라는 구조체도 정의합니다. 두 구조체는 모든 속성에 대해 기본값을 0.0으로 설정합니다.

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()
}

Rect 구조체는 모든 속성에 대해 기본값을 설정하고 있기 때문에 자동으로 기본 이니셜라이저(default initializer)와 memberwise 이니셜라이저를 생성합니다. 이를 사용하여 새로운 Rect 인스턴스를 생성할 수 있습니다.

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
   size: Size(width: 5.0, height: 5.0))

그리고 Rect 구조체를 확장하여 지정된 center 포인트와 크기를 취하는 이니셜라이저를 추가할 수 있습니다.

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)
    }
}

이렇게 추가된 이니셜라이저는 다음과 같이 사용할 수 있습니다.

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

익스텐션으로 정의되어 있는 타입에 새로운 인스턴스 메소드(instance methods)나 타입 메소드(type methods)를 추가할 수 있습니다. 아래 예제는 내장된 Int 타입에 repetitions이라는 새로운 인스턴스 메소드를 추가하고 있습니다.

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

repetitions(task:) 메소드는 타입이 '() -> Void'인 하나의 인자를 전달받는데, 이 인자는 전달받는 파라미터가 없고 값도 리턴하지 않는 함수를 가리킵니다. 이 익스텐션을 정의한 후에 repetitions(task:) 메소드를 어떠한 정수형에서 호출할 수 있습니다.

3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

 

Mutating Instance Methods

익스텐션으로 추가된 인스턴스 메소드는 해당 인스턴스를 수정(modify or mutate)할 수 있습니다. self나 그 속성을 수정하는 구조체나 열거형은 반드시 인스턴스 메소드에 mutating을 붙여주어야 합니다.

 

아래 예제 코드는 내장된 Int 타입에 square라는 새로운 mutating 메소드를 추가합니다.

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt is now 9

Subscripts

익스텐션을 사용하면 정의된 타입에 새로운 서브스크립트를 추가할 수도 있습니다. 다음 예제는 내장된 Int 타입에 정수 서브스크립트를 추가합니다. 이 서브스크립트 [n]은 오른쪽에서부터 n번째 위치한 수를 리턴합니다.

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

익스텐션은 정의된 클래스, 구조체, 열거형에 새로운 nested types를 추가할 수 있습니다. 따로 nested type에 대해서 다루지는 않았지만, 이것에 대해서 알고 싶다면 아래 링크를 참조하시길 바랍니다.

https://docs.swift.org/swift-book/LanguageGuide/NestedTypes.html

 

Nested Types — The Swift Programming Language (Swift 5.6)

Nested Types Enumerations are often created to support a specific class or structure’s functionality. Similarly, it can be convenient to define utility classes and structures purely for use within the context of a more complex type. To accomplish this, S

docs.swift.org

 

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 타입에 새로운 nested enumeration을 추가합니다. Kind라는 이 열거형은 해당 정수를 음수, 0, 양수를 나타내도록 합니다. 또한, kind라는 새로운 computed 인스턴스 속성을 추가하여 해당 정수에 적절한 Kind 열거형을 리턴하도록 합니다.

 

위 Kind 열거형은 Int 값과 함께 다음과 같이 사용될 수 있습니다.

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 값으로 구성된 입력 배열을 전달받아, 이 배열을 순회합니다. 배열의 각 정수에서 함수는 그 정수의 kind 속성에 액세스하여 적절한 description을 출력합니다.

number.kind는 Int.Kind 타입이라는 것을 알고 있습니다. 따라서, Int.Kind 케이스를 switch 문 내에서 사용할 때 Int.Kind.negative라고 전부 쓰지 않고 .negative로 축약하여 사용할 수 있습니다.

 

'프로그래밍 > Swift' 카테고리의 다른 글

[Swift] Protocols (2)  (0) 2022.02.05
[Swift] Protocols (1)  (0) 2022.02.05
[Swift] Type Casting  (0) 2022.01.29
[Swift] Optional Chaining  (0) 2022.01.08
[Swift] Deinitialization  (0) 2022.01.06

댓글