Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

익스텐션 #57

Open
simoniful opened this issue Nov 2, 2022 · 0 comments
Open

익스텐션 #57

simoniful opened this issue Nov 2, 2022 · 0 comments

Comments

@simoniful
Copy link
Owner

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

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

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

Swift에서는 프로토콜을 사용하여 익스텐션에서 요구 사항을 구현하거나, 타입에서 활용할 수 있는 추가 기능을 구성할 수도 있다
프로토콜 확장에 대한 자세한 내용은 Protocol Extensions 페이지에서 더 알아볼 수 있다

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

익스텐션 문법

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

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

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

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

프로토콜 준수에 대한 방법은 Adding Protocol Conformance with an Extension 페이지에서 더 알아볼 수 있다

익스텐션은 기존 제네릭 타입을 확장하는 데 사용할 수도 있다
Extending a Generic Type 페이지에서 더 알아볼 수 있다
조건부로 기능을 추가하기 위해 제네릭 타입을 확장하는 것도 가능하다
Extensions with a Generic Where Clause 페이지에서 더 알아볼 수 있다

기존 타입에 새로운 기능을 더하는 익스텐션을 정의할 경우
익스텐션을 정의하기 전에 생성된 해당 타입의 모든 기존 인스턴스에서 새 기능을 사용할 수 있다

계산 프로퍼티

익스텐션은 계산 인스턴스 프로퍼티와 계산 타입 프로퍼티를 기존 타입에 더할 수 있다
아래 예제는 다섯 개의 계산 인스턴스 프로퍼티를 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"

이러한 계산된 속성은 Double 값이 길이의 특정 단위로 간주되어야 함을 나타낸다
계산 인스턴스 프로퍼티로 구현되지만 이러한 프로퍼티의 이름은 거리 변환을 수행하기 위해 해당 리터럴 값을 사용하는 방법으로
점 구문을 사용하여 부동소수점 리터럴 값에 추가할 수 있다

예제에서 Double 값 1.0은 "1m"를 나타내는 것으로 간주한다
'm' 계산 프로퍼티가 self를 반환하며, 1.m은 Double 값 1.0을 계산하는 것으로 간주한다

다른 단위를 미터 단위로 측정된 값으로 표현하기 위해 일부 변환을 요구한다
1km는 1,000m와 같으므로 km 계산 속성에 1_000.00을 곱하여 미터 단위로 표시되는 숫자로 변환한다
마찬가지로, 3.28084 피트가 1미터라서, 'ft' 계산 프로퍼티는 기본 Double 값을 3.28084로 나누어서 피트에서 미터로 변환한다

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

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

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

이니셜라이저

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

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

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

이니셜라이저 위임과 관련하여 Initializer Delegation for Value Types 페이지에서 더 알아볼 수 있다

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

아래 예시는 기하학적 사각형을 표현하는 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 구조체를 확장할 수 있다

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를 기반으로 적당한 origin 포인트를 계산한다
그런 다음, 이니셜라이저는 구조체의 멤버 이니셜라이저 init(origin:size:)를 호출을 위임하여
적절한 프로퍼티에 새 origin 포인트 및 크기 값을 저장한다

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

repetitions(task:) 메서드는 () -> Void 타입의 단일 인수를 사용한다
해당 인수는 매개 변수가 없고 값을 반환하지 않는 함수를 나타내는 타입

익스텐션을 정의한 후 임의의 정수에 대한 repetitions(task:) 메서드를 호출하여 다음 횟수만큼 작업을 수행할 수 있다

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

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

익스텐션이 더해진 인스턴스 메소드는 인스턴스 자신을 수정하거나 변경할 수도 있다
self나 자신 내부 프로퍼티를 수정하는 구조체나 열거형의 메소드는
원래 구현에서 mutating 메서드와 마찬가지로 반드시 인스턴스 메소드에 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
        }
    }
}

예제에서는 새 중첩 열거형을 Int에 추가한다
Kind 열거형은 특정 정수가 구체적으로 숫자가 음수인지, 0인지, 양수인지를 나타낸다

또한, 해당 정수에 대한 적절한 Kind 열거형 사례를 반환하는
kind라는 새 계산 인스턴스 프로퍼티를 Int에 추가한다

중첩 열거는 이제 모든 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 케이스 값은 switch 구문에서 축약될 수 있다(Int.Kind.negative를 .negative로)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant