티스토리 뷰

Swift 공식 가이드/Swift 3

Extensions

찜토끼 2017. 3. 23. 00:45

Apple 제공 Swift 프로그래밍 가이드(3.0.1)의 Extensions 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.



들어가며

확장Extension을 통해 기존 클래스, 구조체, ENUM, 프로토콜 타입에 새로운 기능을 더할 수 있다. 원본 소스 코드에 접근할 수 없는 타입도 확장할 수 있다. (이렇게 기존 코드를 건드리지 않고 기능을 확장하는 것을 retroactive modeling이라고 부르는 것 같다) Swift의 확장은 Objective-C에서의 카테고리와 유사한데, 다른 점이 있다면 확장은 개별적으로 이름을 따로 가지지 않는다는 것이다.


확장을 통해 기존 타입에 다음과 같은 것들을 적용할 수 있다

  • computed 인스턴스 프로퍼티, computed 타입 프로퍼티 추가

  • 인스턴스 메서드, 타입 메서드 추가

  • 새로운 이니셜라이저 제공

  • 서브스크립트 정의

  • 새로운 nested 타입 정의/사용

  • 특정 프로토콜을 따르게 만들기


Swift에서는 무려 프로토콜도 확장할 수 있다. 프로토콜 확장을 통해 프로토콜의 추가기능additional functionality을 더할 수 있다. 자세한 것을 Protocol Extensions을 참조.

<note> 확장은 타입에 새로운 기능을 더할 수 있지만, 기존 기능을 오버라이드할 수는 없다.



문법

extension 키워드를 사용한다.

// SomeType을 확장
extension SomeType {
    // 새로운 기능을 더한다
}

// SomeType이 SomeProtocol과 AnotherProtocol을 따르게 하기
extension SomeType: SomeProtocol, AnotherProtocol {
    // 프로토콜의 요구사항을 구현한다
}

<note> 기존 타입을 위한 확장을 정의하여 새로운 기능을 더하면, 해당 타입의 인스턴스 모두에 적용이 된다. 심지어 확장이 정의되기 전에 생성되었던 인스턴스도 확장된 새로운 기능을 사용할 수 있다.



Computed Properties

확장을 통해 computed 인스턴스 프로퍼티와 computed 타입 프로퍼티를 기존의 타입에 더할 수 있다.

예제를 통해 살펴보자. Swift의 built-in Double Type을 확장하여, 거리를 표현할 수 있는 5개의 computed 인스턴스 프로퍼티를 더하는 예제이다.

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 // (25.4 / 100.0)
let threeFeet = 3.ft // (3 / 3.28084)
let aMarathon = 42.km + 195.m // (42 * 1000.0 + 195)

<note> stored 프로퍼티나 프로퍼티 옵저버는 확장을 통해 더할 수 없다.



Initializers

확장을 통해 새로운 이니셜라이저를 기존의 타입에 더할 수 있다. (새로운 이니셜라이저를 제공하게 해준다는 말)

  • 새로운 커스텀 타입을 파라미터로 받는 이니셜라이저를 기존의 타입에 더할 수 있다.

  • 클래스에 새로운 convenience 이니셜라이저를 더할 수 있다.

  • 새로운 designated 이니셜라이저, 디이니셜라이저는 더할 수 없다. 이것들은 반드시 클래스의 본래 구현부에서 제공되어야 한다.


<note> 모든 stored 프로퍼티에 디폴트 값을 제공하며 커스텀 이니셜라이저가 하나도 정의되지 않은 값 타입이 있다고 해보자. 이 경우에는 자동으로 디폴트이니셜라이저와 memberwise 이니셜라이저가 제공되는 것을 이니셜라이저 파트에서 배웠다. 만약 이 값 타입에 확장을 통해 커스텀 이니셜라이저를 더한 경우 이것들은 여전히 제공될까? 정답은 여전히 제공된다는 것이다. 커스텀 이니셜라이저를 구현부에서 정의한 것이 아니기 때문이다. 이 경우, 확장을 통해 정의된 이니셜라이저 안에서 디폴트 이니셜라이저와 memberwise 이니셜라이저를 호출하는 것도 가능하다.


예제를 통해 알아보자. 다음은 직사각형을 나타내기 위한 Rect 구조체이다.

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에는 디폴트 이니셜라이저와 memberwise 이니셜라이저가 제공된다
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(), size: Size())


// Rect를 확장하여 이니셜라이저를 더한다
extension Rect {
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        
        // 확장을 통해 convenience 이니셜라이저를 더한 경우이므로
        // memberwise 이니셜라이저는 아직 제공된다
        self.init(point: Point(x: originX, y: originY), size: size)
    }
}

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))

<note> 확장을 통해 이니셜라이를 더했을 때도 그 이니셜라이저 안에서 인스턴스가 완전히 초기화되는 것은 보장되어야 한다.



Methods

확장을 통해 새로운 인스턴스 메서드와 타입 메서드를 기존의 타입에 더할 수 있다.

확장을 통해 Int 타입에 repetitions 라는 인스턴스 메서드를 더하는 예제이다.

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

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


// use trailing closure syntax
3.repetitions{
    print("Hello!")
}
// 결과는 같다


<Mutating 인스턴스 메서드>

구조체/ENUM에서 자신self 혹은 자신의 프로퍼티를 수정modify, mutate하는 인스턴스 메서드는 반드시 mutating 키워드를 붙여야 했다. 확장을 통해 더해진 인스턴스 메서드에서도 같은 룰이 적용된다.

다음은 Int 타입에 mutating 메서드 square를 더하는 예제다.

extension Int {
    mutating func square() {
        self = self * self
    }
}

var someInt = 3
someInt.square()
// someInt is now 9



Subscripts

확장을 통해 새로운 서브스크립트를 기존의 타입에 더할 수 있다.

다음은 Int 타입에 integer 서브스크립트를 더하는 예제이다. 서브스크립트 [n]은 Int 값의 오른쪽에서부터 n번째 10진수를 리턴한다. 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
746381295[9]
// 0746381295[9] 와 같은 요청을 한 것으로 처리되어 return 0



Nested Types

확장을 통해 새로운 Nested Type을 기존의 클래스, 구조체, ENUM에 더할 수 있다.

// Int 타입을 확장
extension Int {
    // 새로운 nested ENUM을 더한다
    enum Kind {
        case negative, zero, positive
    }
    
    // 새로운 computed 프로퍼티를 더한다
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

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])
// "+ + - 0 - 0 +"


'Swift 공식 가이드 > Swift 3' 카테고리의 다른 글

Protocols (2)  (0) 2017.03.25
Protocols (1)  (0) 2017.03.23
Nested Types  (0) 2017.03.23
Type Casting  (0) 2017.03.23
Error Handling  (0) 2017.03.18
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함