Swift/Swift 가이드

[Swift] 공식 문서 - 익스텐션(Extensions)

유정주 2022. 7. 30. 23:13
반응형

새로 배운 점

  • 익스텐션은 원본 코드를 건들지 않고 타입을 확장할 수 있는 기능이며, retroactive 모델링이라고 합니다.
  • 익스텐션은 Objective-C의 카테고리와 유사합니다.
  • 익스텐션으로 designated 이니셜라이저 또는 deinitializer는 추가할 수 없습니다.
  • 만약 익스텐션을 사용하여 모든 저장 프로퍼티에 기본 값이 설정되어 있고, 어떠한 커스텀 이니셜라이저를 정의하지 않은 값 타입에 이니셜라이저를 추가하면, 익스텐션 이니셜라이저에서 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출 할 수 있습니다.
  • 만약 익스텐션을 사용하여 다른 모듈에서 선언된 구조체에 이니셜라이저를 추가하면, 새로운 이니셜라이저는 모듈에 정의된 이니셜라이저를 호출하기 전까지 self에 접근할 수 없습니다.
  • 익스텐션으로 새로운 이니셜라이저를 추가하면, 각 인스턴스는 이니셜라이저가 완료될 때까지 완전히 초기화되도록 확실하게 해줘야 합니다
  • 익스텐션은 정의된 클래스, 구조체, 열거형에 중첩 타입을 추가할 수 있습니다.

 

Extensions

익스텐션(extension)을 사용하면 이미 정의된 클래스, 구조체, 열거형 또는 프로토콜 타입에

새로운 기능을 추가할 수 있습니다.

이는 원본 코드를 건들지 않고 타입을 확장할 수 있는 기능이며, retroactive 모델링이라고 합니다.

익스텐션은 Objective-C의 카테고리와 유사합니다.

 

익스텐션으로 다음과 같은 일을 할 수 있습니다.

  • 연산 인스턴스(computed instance) 프로퍼티와 연산 타입(computed type) 프로퍼티를 추가할 수 있습니다.
  • 인스턴스 메서드나 타입 메서드를 정의할 수 있습니다.
  • 새로운 이니셜라이저를 제공할 수 있습니다.
  • 서브스크립트를 정의할 수 있습니다.
  • 새로운 중첩 타입을 정의하거나 사용할 수 있습니다.
  • 특정 프로토콜을 따르는 타입을 만들 수 있습니다.
익스텐션은 타입에 새로운 기능을 추가할 수는 있지만,
존재하는 기능을 오버라이드할 수는 없습니다.

 

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

익스텐션을 사용하면 존재하는 타입에 연산 인스턴스 프로퍼티나 연산 타입 프로퍼티를 추가할 수 있습니다.

아래 예제는 Swift에 내장된 Double 타입에 5개의 연산 프로퍼티를 추가하고 있습니다.

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 값입니다.

 

이 프로퍼티들은 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"

 

익스텐션은 새로운 연산 프로퍼티를 추가할 수는 있지만
저장 프로퍼티나 프로퍼티 옵저버를 추가할 수는 없습니다.

 

Initializers

익스텐션으로 새로운 이니셜라이저를 추가할 수도 있습니다.

이를 사용하면 타입을 확장하여 이니셜라이저의 매개변수로 커스텀 타입을 주거나

원래 구현에 포함되지 않은 초기화 옵션을 추가할 수도 있습니다.

 

익스텐션으로 클래스에 새로운 편의(convenience) 이니셜라이저를 추가할 수 있지만

designated 이니셜라이저 또는 deinitializer는 추가할 수 없습니다.

Designated 이니셜라이저나 deinitializer는 반드시 원본 클래스 구현에서 제공되어야 합니다.

 

만약 익스텐션을 사용하여 모든 저장 프로퍼티에 기본 값이 설정되어 있고,

어떠한 커스텀 이니셜라이저를 정의하지 않은 값 타입에 이니셜라이저를 추가하면,

익스텐션 이니셜라이저에서 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출 할 수 있습니다.

값 타입의 원본 구현에 이니셜라이저를 작성한 경우는 해당되지 않습니다.

 

만약 익스텐션을 사용하여 다른 모듈에서 선언된 구조체에 이니셜라이저를 추가하면,

새로운 이니셜라이저는 모듈에 정의된 이니셜라이저를 호출하기 전까지

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 구조체는 모든 프로퍼티에 대해 기본값을 설정하고 있기 때문에

자동으로 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 생성합니다.

이를 사용하여 새로운 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

익스텐션으로 정의되어 있는 타입에 새로운 인스턴스 메서드나 타입 메서드를 추가할 수 있습니다.

아래 예제는 내장된 Int 타입에 repetitions라는 새로운 인스턴스 메서드를 추가합니다.

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

repetitions(task:)는 타입이 () -> Void 인 매개변수를 가지는데,

이 매개변수는 전달 받는 파라미터가 없고 값도 리턴하지 않는 함수를 가리킵니다.

이 익스텐션을 정의한 후 repetitions(task:)를 어떠한 정수형에서 호출할 수 있습니다.

 

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값이 요청된 index만큼 없다면 이 서브스크립트는 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 타입에 새로운 중첩 열거형을 추가합니다.

Kind 라는 열거형은 해당 정수를 음수, 0, 양수를 나타냅니다.

새로운 연산 프로퍼티인 kind는 적절한 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 + "

printIntengerKinds(_:)는 Int 값으로 구성된 입력 배열을 전달 받아,

이 배열을 순회합니다.

배열의 각 정수에서 함수는 그 정수의 kind 프로퍼티에 접근하여 적절한 description을 출력합니다.

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

 

참고

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

 

 

반응형