Swift Language Guide: Extension

J.Noma·2021년 11월 13일
0

Swift : 문법

목록 보기
8/11

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


🐣 서론

Extension은 이미 존재하는 class/struct/enum/protocol에 새로운 기능을 추가합니다
이는 원본 소스코드에 접근권한이 없어도 확장할 수 있습니다
(이렇게 원본을 수정하지 않고 재사용하여 새로운 컨셉을 표현하는 것을 retroactive modeling이라 합니다)

Extension은 Objective-C에서의 category와 비슷한 개념이나, extension은 이름을 갖지 않는다는 차이가 있습니다

Extension은 이런게 가능합니다

  • 계산 프로퍼티 추가 (저장 프로퍼티/프로퍼티 옵저버는 안됨)
  • 메서드 추가
  • 생성자 추가
  • subscript 추가
  • Nested type 추가
  • 프로토콜 생성/채택

✔️ 저장 프로퍼티는 왜 안될까?

✅NOTE
저장 프로퍼티는 왜 안될까?
저장 프로퍼티가 늘어난다는 것은 타입이 요구하는 메모리 사이즈가 변경됨을 뜻합니다
Extension은 일부 코드 영역에만 적용할 수 있으므로 하나의 타입에 대해 각 모듈마다 서로 다른 메모리 사이즈를 요구하게 됩니다
그래서, 컴파일러는 어떤 타입이 요구하는 메모리 사이즈를 정의할 때, 모든 모듈을 뒤지며 이 타입에 대한 extension이 있는지+이 모듈에선 얼만큼의 메모리가 필요한지 계산해야 합니다
애플의 공식적인 답변은 아니지만, 컴파일러에게 이 작업이 부담이기에 막았다고 합니다
(모듈간 인스턴스를 이동시킬 때도 골치아플듯?)
참고 레딧글

Swift에서는 심지어 프로토콜을 extension할 때는, 요구사항이 아닌 '구현체' 자체를 추가할 수도 있습니다

❗️주의❗️
Extension은 새로운 기능을 추가할뿐, 기존의 기능을 override하진 못합니다


🐧 Extension 구문

  • 기본 예제
extension SomeType {
    // new functionality to add to SomeType goes here
}
  • 프로토콜 채택 예제
extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

✅NOTE
Extension은 런타임에 추가하는 개념이 아닙니다.
따라서, 어느 코드 위치에 선언하든 해당 타입의 모든 인스턴스에 영향을 미칩니다


🐱 계산 프로퍼티

계산 프로퍼티를 추가할 수 있습니다

  • 예제
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"

서론에서 언급했듯이, 저장프로퍼티와 프로퍼티옵저버는 extension으로 추가할 수 없습니다


🐭 생성자

새로운 생성자를 추가할 수도 있습니다
이를 통해, 우리가 정의한 custom 타입을 생성자의 파라미터로 받게 한다던지 원래 정의부에는 없던 추가적인 생성자 옵션을 줄 수 있습니다

extension으로 만든 생성자 역시 인스턴스를 fully initialize할 책임이 있다는걸 기억합시다 (= 모든 저장 프로퍼티 초기화)

✔️ Class는 convenience만 가능하다

class는 designated 생성자와 소멸자를 추가할 수 없고 원본을 따라야 합니다
'convenience 생성자'만 추가할 수 있습니다

  • Why?
    참고글에서 이유 중 하나로 designated 생성자 extension이 'subclass의 생성자가 없으면 superclass의 생성자를 상속하는 기제'를 위협한다는 의견이 있음

✔️ 값타입의 default/memberwise 생성자는 지켜준다

우선 값타입은 새로운 생성자를 class대비 자유롭게 추가할 수 있습니다

오히려 좀 더 우대(?)를 해주는 사항이 있는데

'default생성자' 혹은 'memberwise생성자'를 자동으로 받을 수 있는 케이스에서

  1. extension에 생성자를 추가한다고 해서 이 자동생성 조건이 깨지지 않습니다
    (원래, default와 memberwise는 custom생성자가 하나라도 있으면 자동생성되지 않는다)

  2. extension에 추가할 새로운 생성자에서 이들을 호출할 수도 있습니다

✔️ 다른 모듈에 정의된 struct를 extend할 때 주의사항

다른 모듈에 정의된 struct를 생성자 extension하려면, self에 접근할 수 없는 문제가 있다

original에 정의된 생성자를 호출하여, 저장 프로퍼티를 모두 초기화한 후에야 접근할 수 있다
(마치 class가 convenience 쓰듯)


🐹 메서드

메서드는 별다른 특이사항없이 추가가 가능합니다

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

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

mutating 인스턴스 메서드
값타입에 대해 self를 수정하는 메서드를 추가할 때는, original 구현과 똑같이 mutating 키워드를 붙혀주어야 합니다


🐻 Subscripts

subscript도 별다른 특이사항없이 추가가 가능합니다

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}

🐼 Nested Types

Nested type 또한 특이사항없이 extension으로 추가할 수 있습니다

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

❗️주의❗️
Protocol의 Extension에서는 Nested Type을 확장할 수 없습니다

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글