공식 문서로 공부하는 Swift (19) - 익스텐션

ci·2020년 5월 31일
1

Extensions

익스텐션(extensions)은 기존 클래스, 구조체, 열거형, 프로토콜 타입에 새로운 기능을 더하는 것이다. 원래 소스 코드에 접근하지 않아도 그 타입에 대한 기능을 확장할 수 있다. 익스텐션은 Objective-C의 카테고리와 유사하다. Objective-C의 카테고리와 달리 Swift의 익스텐션은 이름을 갖지 않는다.

Swift의 익스텐션을 이용해 할 수 있는 것

  • 계산 인스턴스 프로퍼티와 계산 타입 프로퍼티의 추가.
  • 인스턴스 메소드와 타입 메소드 정의.
  • 새로운 이니셜라이저 제공.
  • 서브스크립트 정의.
  • 새로운 중첩 타입 정의 및 사용.
  • 기존 타입을 프로토콜을 따르게 하기.

Swift에서는 요구사항을 구현하거나 새로운 기능을 더하기 위해 프토토콜을 확장할 수도 있다.

익스텐션은 타입에 새로운 기능을 더할 수는 있지만 기존 기능을 오버라이드 할 수는 없다.



익스텐션 문법

extension 키워드를 사용하여 익스텐션을 선언한다.

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

익스텐션은 기존 타입을 하나 이상의 프로토콜을 준수하도록 확장할 수 있다. 이를 위해 클래스나 구조체와 같은 방법으로 프로토콜 이름을 작성한다.

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

익스텐션은 기존 제네릭 타입을 확장하는 데 사용할 수도 있다. 조건부로 기능을 추가하기 위해 제네릭 타입을 확장하는 것도 가능하다.

기존 타입에 새로운 기능을 더하는 익스텐션을 정의할 경우 새로운 기능은 그 타입의 모든 인스턴스에서 사용 가능해야 한다. 익스텐션을 정의하기 이전에 만들어진 것들에도 그래야 한다.



계산 프로퍼티

익스텐션은 계산 인스턴스 프로퍼티와 계산 타입 프로퍼티를 기존 타입에 더할 수 있다. 이 예제는 다섯 개의 계산 인스턴스 프로퍼티를 Swift의 Double 타입에 더한다.

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"

이 프로퍼티는 읽기 전용 프로퍼티라서 간결성을 위해 get 키워드 없이 표현된다. 반환 값은 Double이고, Double을 수용 하는 수학적 계산에 사용할 수 있다.

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

익스텐션은 새로운 계산 프로퍼티를 더할 수는 있지만 새로운 저장 프로퍼티나 프로퍼티 옵저버를 더할 수는 없다.



이니셜라이저

익스텐션은 기존 타입에 새로운 이니셜라이저를 추가할 수 있다. 다른 타입이 이니셜라이저 매개 변수에서 커스텀 타입을 받아들이도록 확장할 수 있다. 또는 타입의 원래 구현에 포함되지 않았던 추가적인 초기화 옵션을 제공할 수도 있다.

익스텐션은 클래스에 편리한 이니셜라이저를 더할 수도 있다. 지정 이니셜라이저나 디이니셜라이저를 더할 수는 없다. 지정 이니셜라이저와 디이니셜라이저는 반드시 원래 클래스 구현에서만 제공되어야 한다.

만약 익스텐션을 값 타입에 이니셜라이저를 추가하는데 사용하고, 그 값 타입이 모든 프로퍼티에 대해 기본 값을 제공하는 동시에 커스텀 이니셜라이저를 정의하지 않았다면, 기본 이니셜라이저와 멤버 이니셜라이저를 익스텐션의 이니셜라이저에서 호출할 수 있다. 값 타입의 오리지널 구현에서 이니셜라이저 코드를 작성했다면 해당하지 않는다.

다른 모듈에 선언된 구조체에 이니셜라이저를 추가하고자 할 경우, 새로운 이니셜라이저는 모듈에 정의된 이니셜라이저를 호출하기 전까지 self에 접근할 수 없다.


아래 예시는 기하학적 사각형을 표현하는 Rect 구조체와 이를 지원하는 SizePoint 구조체를 정의한다.

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 구조체를 확장할 수 있다.

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

새로운 이니셜라이저는 centersize를 통해 적당한 origin 포인트를 계산한다. 다음으로 이니셜라이저는 구조체의 멤버 이니셜라이저 init(origin: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)

메소드

익스텐션은 새로운 인스턴스 메소드와 타입 메소드를 기존 타입에 추가할 수 있다. 아래 예제는 Int 타입에 새로운 인스턴스 메소드 repetitions(task:)를 추가한다.

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

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

변경 가능한 인스턴스 메소드

익스텐션이 더해진 인스턴스 메소드는 인스턴스 자신을 수정할 수도 있다. self나 프로퍼티를 수정하는 구조체나 열거형의 메소드는 반드시 인스턴스 메소드에 mutating 키워드를 붙여야 한다.


아래 예시는 Int 타입에 변경 가능한 메소드 square를 추가한다.

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


서브스크립트

익스텐션은 기존 타입에 새로운 서브스크립트를 더할 수 있다. 아래 예시는 Int 타입에 정수형 서브스크립트를 추가한다. 서브스크립트 [n]은 숫자의 오른쪽으로부터 n번째 자리에 위치한 10진수를 반환한다.

  • 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

요청되는 인덱스를 위한 자리 수가 충분하지 않다면 이 서브스크립트는 0을 반환한다. 숫자는 왼쪽이 0으로 채워지기 때문이다.

746381295[9]
// returns 0, as if you had requested:
0746381295[9]


중첩 타입

익스텐션은 기존 클래스, 구조체, 열거형에 새로운 중첩 타입을 추가할 수 있다.

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

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 + "

number.kindInt.Kind 타입으로 알려져 있다. 때문에 모든 Int.Kind 케이스 값은 switch 구문에서 축약될 수 있다(Int.Kind.negative.negative로).

0개의 댓글