호댕의 iOS 개발

[Swift 기본 문법] Extension 공식문서 본문

Software Engineering/Swift

[Swift 기본 문법] Extension 공식문서

호르댕댕댕 2022. 4. 17. 10:53

Swift Programming LauguageGuide를 보고 작성한 글입니다. 

 

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

 

📖 Extension은 뭘까?

Extension은 이미 존재하는 클래스, 구조체, 열거형, 프로토콜 타입에 새로운 기능을 추가하기 위해 존재한다. 

이는 원래 코드에 접근할 수 없는 type을 확장하는 기능을 한다. 

이는 Objective-C에서 categories와 유사하다. (단 Extension의 경우 따로 이름이 없다)

 

Extension의 기능

  • 연산 인스턴스 프로퍼티 연산 타입 프로퍼티를 추가한다
  • 인스턴스 메서드 타입 메서드를 정의한다
  • 새로운 이니셜라이저를 제공한다
  • subscript를 정의한다
  • 새로운 중첩 타입을 정의하거나 사용한다
  • 이미 존재하는 타입에 프로토콜을 채택한다

Swift에선 프로토콜에 Extension을 활용하여 프로토콜에 대한 기본 구현을 할 수도 있다.

 

✏️ Extension 작성은 어떻게 할까?

타입을 선언할 때처럼 대문자 카멜케이스를 사용해 이름을 작성하고 앞에 extension 키워드를 붙여주면 된다.

extension TypeName {
	// 추가할 기능에 대해 작성해주면 됨
}

 

또한 extension에 하나 이상의 프로토콜을 채택할 수 있다. 이는 클래스나 구조체에서 하던 것처럼 그대로 해주면 된다. 

프로토콜 필수 요구사항은 extension 내부에 구현해주면 된다.

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

 

🧮 Computed Properties

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"

이렇게 기능을 추가하여 computed property가 Double 값이 특정 길이 단위로 표현되어야 함을 나타낸다. 

이는 연산 프로퍼티를 사용할 때처럼 dot syntex를 통해 사용하면 된다. 

 

⛔️ 여기서 주의할 점!! 

Extension은 새로운 computed property는 추가할 수 있지만 stored perperty는 추가할 수 없다. 또한 기존 프로퍼티의 property observer도 추가할 수 없다. 

(이는 기존 타입 내에서만 추가 가능)

 

 

🏁 Initializer

Extension에선 기존에 존재하는 타입에 새로운 이니셜라이저를 추가할 수 있다. 

이를 통해 타입을 원래 구현할 때 포함되지 않았던 초기화 옵션을 추가로 제공할 수 있고, 다른 타입을 확장하여 사용자 정의 타입을 초기화의 매개변수로 사용할 수도 있다. 

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

이런 3개의 사용자 정의 타입이 있고 Rect 타입에 Point와 Size의 인스턴스를 생성했다고 가정해보자. 

 

그럼 Extension을 통해 사용자 정의 타입을 매개변수로 초기화를 해줄 수 있다. 

물론 Rect은 구조체이기 때문에 기본 이니셜라이저나 멤버와이즈 이니셜라이저를 사용할 수 있다. 

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

하지만 이런 식으로 Extension에 이니셜라이저를 정의해줄 수도 있다.

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을 계산할 수 있는 것이다.

 

🔧 Method

Extension에서 새로운 인스턴스 메서드나 타입 메서드를 추가할 수 있다. 

이렇게 추가해준 메서드는 나중에 기존 메서드를 사용하는 것처럼 그대로 사용할 수 있다. 

 

또한 Extension을 통해 추가된 인스턴스 메서드의 경우 인스턴스 자체를 수정 및 변경할 수도 있다. 즉 구조체나 열거형에서 self 프로퍼티를 수정하는 메서드의 경우 반드시 mutating 키워드를 붙여야 하는데 Extension에도 이를 구현할 수 있는 것이다. 

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

 

🧩 Subcripts

Extension에 새로운 subcript도 정의할 수 있다. 

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에 subscript 문법으로 자리수를 찾을 수 있는 것을 구현할 수도 있는 것이다. 

 

🪆 Nested Types

기존 클래스, 구조체, 열거형에 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
        }
    }
}

예시처럼 Int 타입에 Kind라는 열거형을 중첩타입으로 새롭게 구현할 수도 있는 것이다.

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

이렇게 연산 프로퍼티인 kind에 Kind 타입을 반환하도록 하여 사용할 수도 있다.

 

 

그렇다면 Extension에서 함수를 override할 수 있을까??

공식 문서에선 일단 이는 안된다고 말하고 있다. 

class SuperClass { }

extension SuperClass {
    @objc func otherNumber() -> Int {
        return 1
    }
}

class Subclass: SuperClass { }

extension Subclass {
    override func otherNumber() -> Int {
        return 2
    }
}

var superClass = SuperClass()
var subClass = Subclass()
print(superClass.otherNumber()) // 1
print(subClass.otherNumber()) // 2

하지만 override func 앞에 @objc 키워드를 붙이면 정상적으로 override가 가능하긴 하다. 

 

Objective-C와의 호환성을 위해 Extension에서 override가 가능한 것이다. 

하지만 SuperClass에서도 @objc 함수를 Extension에 작성해야 한다. 

 

위처럼 Objective-C에선 저렇게 Extension에서 override를 하는 것이 가능하나, Swift에선 원칙적으로 extension에서 override를 하는 것을 금지하고 있다. 

Comments