Extensions

Swift/Language Guide 2018. 9. 18. 00:05
반응형

[최종 수정일 : 2018.09.05]

원문 : 애플 개발 문서 Swift 4.2 Language Guide - Extensions

확장(Extensions)

확장(Extensions)은 기존에 있는 클래스, 구조체, 열거형, 프로토콜 타입에 새로운 기능을 추가합니다. 원래 소스코드(소급 모델링(retroactive modeling))에 접근하지 못하는 타입을 확장하는 능력도 포함합니다. 확장은 Objective-C에서의 카테고리와 비슷합니다.(Objective-C 카테고리와 다르게, Swift의 확장은 이름을 가지지 않습니다)

Swift에서 확장이 할수 있는 것들:

  • 계산 인스턴스 프로퍼티와 계산 타입 프로퍼티를 추가
  • 인스턴스 메소드와 타입 메소드를 정의
  • 새로운 초기화 제공
  • 서브스크립트 정의
  • 새로 중첩된 타입을 정의하고 사용
  • 기존 타입에 프로토콜을 준수하도록 만들기

Swift에서, 요구사항의 구현을 제공하거나 이를 준수하는 타입을 활용하여 추가적인 기능을 추가하기 위해 프로토콜을 확장 할수 있습니다. 더 자세한 것은 프로토콜 확장(Protocol Extensions)를 보세요.

주의
확장은 타입에 새로운 기능을 추가할 수 있지만, 기존 기능들을 오버라이딩할 수는 없습니다.

확장 문법(Extension Syntax)

extension 키워드로 확장 선언하기

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

확장(extension)은 기존 타입을 하나 이상의 프로토콜을 채택하도록 확장할 수 있습니다. 프로토콜을 추가적으로 준수(conformance)하기 위해, 클래스나 구조체를 작성하는 것과 같은 방법으로 프로토콜 이름을 작성합니다.

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

확장으로 프로토콜 준수하도록 추가하기(Adding Protocol Conformance with an Extension)에서 설명된 것과 같은 방법으로 프로토콜 준수하도록 추가합니다.

확장은 제네릭 타입 확장하기(Extending a Generic Type)에서 설명된 것처럼, 기존 제네릭 타입을 확장하기 위해 사용될 수 있습니다. 조건부로 기능을 추가하기 위해 제네릭 Where절로 확장하기(Extensions with Generic Where Clause)에서 설명된 것 처럼, 제네릭 타입을 확장할 수 있습니다.

주의
기존 타입에 새로운 기능을 추가하기 위해 확장을 정의하는 경우, 새로운 기능은 타입의 모든 기존 인스턴스에서 사용가능합니다. 심지어는 확장이 정의되기 전에 생성된 인스턴스에서도 사용할 수 있습니다.

계산 프로퍼티(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 값을 길이의 특정 단위로 표현합니다. 비록 게산된 프로퍼티로 구현되었지만, 이러한 프로피티의 이름은 거리 변환을 수행하기 위해 리터럴 값을 사용하는 방법처럼, 부동소수점 리터럴 값에 점(dot .) 문법으로 사용할 수 있습니다.

이 예제에서, Double인 1.0값은 1 미터를 나타내는 것으로 간주됩니다. 이것이 계산 프로퍼티 m이 self를 반환하는 이유입니다. - 1.m표현식은 Double타입의 1.0값으로 계산한하는 것으로 간주합니다.

다른 단위는 미터로 측정된 값 처럼 표현하기 위해 약간의 변환이 필요합니다. 1킬로미터는 1,000미티와 같으며, km 계산 프로퍼티는 미터로 변환하기 위해 1_000.00 을 곱합니다. 비슷하게, 1 미터는 3.28084 피트(feet)이고, ft 게산 프로퍼티는 피트를 미터로 변환하기 위해, Double 값을 3.28084로 나눕니다.

이러한 프로퍼티는 읽기전용(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"

주의
확장은 새로운 계산 프로퍼티를 추가할 수 있지만, 저장 프로퍼티를 추가하거나 기존 프로퍼티를 감시(observers)하는 프로퍼티를 추가 할수는 없습니다.

초기화(Initializers)

확장은 기존 타입에 새로운 초기화를 추가할 수 있습니다. 이것은 초기화 매개변수로 사용자 정의 타입을 허용하는 다른 타입이나 타입의 원래 구현에 포함되지 않는 추가적인 초기화 옵션을 제공하여 확장 가능합니다.

확장은 클래스에 새로운 편리한 초기화(convenience initializer)를 추가할 수 있지만, 새로운 지정된 초기화(designated initializer)나 클래싀 해제(deinitializers)를 추가할 수는 없습니다. 지정된 초기화와 해제는 항상 원래 클래스 구현에 의해서 제공되어야 합니다.

모든 저장 프로퍼에 값 타입에 기본 값을 제공합니다.

값 타입에 초기화를 추가하기 위해 확장을 사용하는 경우 모든 저장 프로퍼티에 대해 기본 값을 제공하고 사용자정의 초기화를 정의하지 않으며, 확장의 초기화에서 값 타입에 대해 기본 초기화와 멤버단위 초기화를 호출 할 수 있습니다. 값 타입에 대한 초기화 위임(Initializer Delegation for Value Types)에 설명된 것처럼, 값 타입의 원래 구현에서 초기화를 작성하는 경우에는 그렇지 않습니다.

구조체에 초기화를 추가하기 위해 확장을 사용하는 경우 다른 모듈에 선언할수 있으며, 새로운 초기화는 정의한 모듈에서 초기화가 호출될때까지는 self에 접근 할 수 없습니다.

아래 예제는 기하학적인 사각형을 포현하기 위해 사용자 정의 구조체 Rect를 정의합니다. 또한, 이 예제는 지원하는 구조체 Size와 Point 2개를 정의하며, 둘다 모든 프로퍼티에 대해 기본 값 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 Initializers)에 설명된 것처럼, 기본 초기화와 멤버단위 초기화를 자동적으로 받아들입니다. 이러한 초기화는 새로운 Rect인스턴스를 생성하는데 사용될 수 있습니다.

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

특정 중심점(center)과 크기(size)를 받는 추가적인 초기화를 제공하기 위해 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)
    }
}

새로운 초기화는 제공된 center점과 size값을 기반으로 적절한 원점을 계산하는 것으로 시작합니다. 초기화는 구조체에서 적절한 프로퍼티에 새로운 원점과 크기 값을 저장하는 멤버단위 초기화 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)

주의
확장으로 새로운 초기화를 제공하는 경우, 여전히 각 인스턴스가 한번 초기화되면 완전히 초기화되도록 해야 합니다.

메소드(Methods)

확장은 기존 타입에 새로운 인스턴스 메소드와 타입 메소드를 추가할 수 있습니다. 다음에 오는 예제는 Int타입에 새로운 인스턴스 메소드 repetitions를 추가합니다.

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

repetitions(task:) 메소드는 매개변수 없고 반환 값이 없는 함수를 가리키는 () -> Void타입의 인자 하나를 가집니다.

확장을 정의하고난 후에, 모든 정수형에서 많은 횟수만큼 작업을 수행할 수 있는 repetitions(task:) 메소드를 호출할 수 있습니다.

인스턴스 메소드 변경하기(Mutating Instance methods)

인스턴스 자체를 수정(또는 변경) 할 수 있는 인스턴스 메소드를 확장으로 추가됩니다. 구조체와 열거형 메소드는 self나 프로퍼티를 수정하려면 원래 구현으로 부터 변경하는(mutating) 메소드로 만드는 것처럼, 반드시 mutating으로 인스턴스 메소드를 표시 해줘야 합니다.

아래 예제는 Swift의 Int타입에 원래 값을 제곱(squares)하는 새로운 변경하는(mutating) 메소드 square를 호출합니다.

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

서브스크립트(Subscripts)

확장은 기존 타입에 새로운 서브스크립트를 추가할 수 있습니다. 이 예제는 정수형 서브스크립트를 Swift 기본 Int타입에 추가합니다. 이 서브스크립트 [n]은 숫자의 오른쪽으로 부터 위치를 가져오는 십진수(decimal digit) n을 반환합니다.

  • 123456789[0]은 9를 반환
  • 123456789[1]은 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으로 채워진(padded) 경우에, 서브스크립트는 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, 양수인지 표현합니다.

또한, 이 예제는 Int에 정수형에 대해 적절한 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를 처리하고, 적절한 설명을 출력합니다.

주의
number.kind는 이미 Int.Kind 타입으로 알려져 있습니다. 이 때문에, Int.Kind의 모든 케이스(case)의 값은 switch 문 안쪽에서 Int.Kind.negative 보다는 .negative처럼 짧게 작성할 수 있습니다.


반응형

'Swift > Language Guide' 카테고리의 다른 글

Memory Safety  (0) 2018.09.18
Automatic Reference Counting  (0) 2018.09.18
Generics  (0) 2018.09.18
Protocols  (0) 2018.09.18
Nested Types  (0) 2018.09.18
Type Casting  (0) 2018.09.18
Error Handling  (0) 2018.09.18
Optional Chaining  (0) 2018.09.18
Posted by 까칠코더
,