소피it블로그

[Swift 공식문서] Extensions 정리 본문

개발_iOS/스위프트

[Swift 공식문서] Extensions 정리

sophie_l 2022. 8. 10. 22:59

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

 

Extensions — The Swift Programming Language (Swift 5.7)

Extensions Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you don’t have access to the original source code (known as retroactive modeling). Extensions

docs.swift.org

익스텐션은 이미 존재하는 클래스, 구조체, 에뉴머레이션, 또는 프로토콜 타입에 새로운 기능을 추가한다. 오리지널 소스 코드에 접근이 없는 타입에 대한 확장도 할 수 있는데, 이를 소급 모델링이라고 부르기도 한다.

익스텐션은 옵젝티브-C에서의 카테고리와 비슷하다. 그러나 스위프트의 익스텐션은 이름을 갖지 않는다.

스위프트의 익스텐션은 다음과 같은 것을 할 수 있다:

 

  • 계산 인스턴스 프라퍼티와 계산 타입 프라퍼티를 추가할 수 있다.
  • 인스턴스 메서드와 타입 메서드를 정의할 수 있다.
  • 새로운 이니셜라이저를 제공할 수 있다.
  • subscript를 정의할 수 있다.
  • 새로운 nested 타입을 정의하고 사용할 수 있다.
  • 이미 존재하는 타입이 프로토콜에 순응하도록 할 수 있다.

스위프트에서는 프로토콜을 확장하여 이에 순응하는 타입이 사용할 수 있도록 요건을 구현하거나 기능을 추가할 수도 있다. 자세한 사항은 프로토콜 익스텐션을 참고할 것.

주의할 점은, 익스텐션은 어느 타입에 새로운 기능을 추가할 수 있을 뿐 이미 존재하는 기능을 오버라이딩 할 수는 없다는 것이다.

 

1. 익스텐션 구문

 

익스텐션은 extension 키워드로 정의한다.

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

익스텐션은 이미 존재하는 타입을 확장함으로써 하나 이상의 프로토콜을 적용시킬 수 있다. 프로토콜 순응을 추가하고 싶다면 클래스나 구조체를 적어주는 것과 같은 방법으로 프로토콜 이름을 적어주면 된다.

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

주의할 점은, 이미 존재하는 타입에 새로운 기능을 추가하기 위해 익스텐션을 정의할 경우, 새 기능은 익스텐션이 정의되기 전에 만들어진 해당 타입의 모든 존재하는 인스턴스에서도 접근 가능하다는 점이다.

 

2. 계산 프라퍼티

 

익스텐션은 이미 존재하는 타입에 대해 계산 인스턴스 프라퍼티나 계산 타입 프라퍼티를 추가할 수 있다. 이하의 예시는 스위프트에 내재된 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 값이 길이의 특정 단위로 여겨져야 함을 표현하낟. 계산 프라퍼티로 구현되었지만 이 프라퍼티들이 이름은 실수값 뒤에 도트 구문을 통해 붙여질 수 있으며, 이를 통해 해당 값 그대로 거리 변환을 수행할 수 있다.

이 예시에서 1.0의 Double값은 1 미터를 나타내는 것으로 여겨진다. 따라서 m 계산 프라퍼티는 self를 리턴한다. 즉 1.m은 1.0이라는 Double값을 계산한다.

다른 단위들은 미터로 계산된 값을 표현하기 위해 조금의 변환이 필요하다. 1 킬로미터는 1,000 미터와 같기 때문에 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"

주의할 점은, 익스텐션은 새로운 계산 프라퍼티는 추가할 수 있는 반면 저장 프라퍼티나 이미 존재하는 프라퍼티에 대한 프라퍼티 옵저버는 추가할 수 없다는 점이다.

 

3. 이니셜라이저

 

익스텐션은 기존의 타입에 새로운 이니셜라이저를 추가할 수 있다. 이를 통해 다른 타입들을 확장하여 자신만의 커스텀 타입을 이니셜라이저 매개변수로 사용할 수 있게 되고, 또한 타입 본래의 구현의 일부가 아니었던 추가적인 이니셜라이징 옵션을 제공할 수도 있다.

익스텐션은 클래스에 새로운 convenience 이니셜라이저를 추가할 수는 있으나 designated 이니셜라이저나 디이니셜라이저는 추가할 수 없다. designated 이니셜라이저와 디이니셜라이저는 항상 오리지널 클래스 구현과 함께 제공되어야만 한다.

만약 모든 저장 프라퍼티에 디폴트 값을 주고 커스텀 이니셜라이저를 정의하지 않는 밸류 타입에 이니셜라이저를 추가하기 위해 익스텐션을 사용한다면 익스텐션의 이니셜라이저 안에서 그 밸류 타입의 디폴트 이니셜라이저와 멤버와이즈 이니셜라이저를 호출할 수 있다. 만일 밸류 타입을 본래 구현할 때부터 이니셜라이저를 적어줬을 경우에는 해장되지 않는다.

만약 다른 모듈에서 선언된 구조체에 이니셜라이저를 추가하기 위해 익스텐션을 사용한다면, 기존 모듈로부터 이니셜라이저를 불러오기 전까지는 새 이니셜라이저는 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 구조체를 확장하여 특정한 센터 point와 size를 갖는 추가적인 이니셜라이저를 제공할 수 있다.

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

이 새로운 이니셜라이저는 제공된 센터 point와 size 값에 기반해 적절한 origin point를 계산하는 것으로 시작한다. 이 이니셜라이저는 그 후에 구조체의 자동 멤버와이즈 이니셜라이저 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)

4. 메서드

 

익스텐션은 기존의 타입에 새로운 인스턴스 메서드와 타입 메서드를 추가할 수 있다. 이하의 예시는 Int 타입에 repetitions라고 불리는 새로운 인스턴스 메서드를 추가한다.

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

repetitions(task:) 메서드는 () -> Void 타입의 인수를 하나 받는데, 이는 매개변수도 리턴 값도 없는 함수를 나타낸다.

이 익스텐션을 정의한 후에 repetitions(task:) 메서드를 아무 정수에나 호출하여 해당 숫자만큼 task를 수행하게 할 수 있다.

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

mutating 인스턴스 메서드

 

익스텐션에 추가된 인스턴스 메서드는 인스턴스 그 자체를 수정(mutate)할 수 있다. self나 그 프라퍼티를 변형하는 구조체나 에뉴머레이션 메서드는 본래의 구현에서의 mutating 메서드처럼 해당 인스턴스 메서드를 mutating으로 표시해줘야 한다.

이하의 예시는 스위프트의 Int 타입에 square라고 하는 새로운 mutating 메서드를 추가하는데, 이는 본래의 값을 제곱해준다.

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

5. Subscripts

 

익스텐션은 기존의 타입에 새로운 서브스크립트 또한 추가할 수 있다. 이하의 예시에서는 스위프트에 내재된 Int 타입에 integer 서브스크립트를 추가한다. 이 서브스크립트 [n]은 숫자의 오른쪽에서부터 10진수로 숫자 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으로 채워져있는 것처럼 0을 리턴한다.

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

6. 중첩 타입

 

익스텐션은 기존의 클래스, 구조체, 에뉴머레이션에 대해 새로운 중첩 타입을 추가할 수 있다.

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 값의 배열을 입력값으로 취하고 그 값들을 iterate한다. 배열 내의 각각의 정수에 대해 함수는 그 정수의 kind 계산 프라퍼티를 고려하여 적절한 설명을 출력한다.