티스토리 뷰

Swift 공식 가이드/Swift 3

Enumerations

찜토끼 2017. 2. 27. 15:28

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


들어가며

Enumeration(이하  ENUM)은 관련된 값들을 그룹으로 묶어서 common type 으로 정의해준다. ENUM을 이용하면 타입에 안전한(type-safe) 코드를 작성할 수 있다.

C에서의 ENUM은 각 케이스마다 Integer 값을 할당했었다. 이것을 raw value라고 한다. Swift에서의 ENUM은 raw value로 여러가지 타입을 사용할 수 있고 심지어 항상 raw value 를 할당하지 않아도 된다. 또한 케이스 각각에 연관된 값들(associated values)을 지정할 수 있다.

Swift에서의 ENUM은 기존에는 class에서만 지원되던 기능들을 많이 제공해준다. 예를들면, 계산된 프로퍼티(computed properties), 인스턴스 메서드 등이 있다. 또한 ENUM의 이니셜라이저를 정의할 수도 있고, 기능을 확장시킬 수도 있고, 특정 프로토콜을 따르게 할 수도 있다. 자세한 것은 나중에 살펴보자.



Enumeration Syntax

enum 키워드를 통해 정의할 수 있다.

case 키워드를 통하여 enumeration case 들을 정의할 수 있다.

enum CompassPoint { // 대문자로 시작하게끔
    case north // c, objective-c 와 다르게 자동으로 0부터 채워지지 않는다
    case south
    case east
    case west
}

enum Planet { 
    case mercury, venus, earth, mars, jupiter, saturn, 
          uranus, neptune
    // 콤마로 구분하여 정의 가능
    // 복수형말고 단수형이 자명하다
}


이렇게 정의한 ENUM을 변수에 할당하는 법을 알아보자.

var directionToHead = CompassPoint.west
// 변수가 CompassPoint ENUM 타입이라고 유추되는 타이밍은
// west 케이스로 초기화가 될 때이다

directionToHead = .east
// 한 번 타입이 판단된 변수는 이런 식으로
// ENUM 이름을 생략하고 값을 할당할 수 있다



Matching Enumeration Values with a Switch Statement

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// prints "Watch out for penguins"


// <주의> ENUM의 모든 케이스를 포함할 수 있어야 한다.
// 그게 안 된다면 default 케이스를 사용하자.
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Mostly harmless")
default:
    print("Not a safe place for humans")
}
// prints "Mostly harmless"



Associated Values

ENUM의 케이스 각각에 연관된 다른 타입의 값들을 저장할 수 있다. 따라서 필요한 추가정보를 저장할 수 있으며, ENUM 변수에 접근할 때마다 이 정보를 변경하는 것도 가능하다. 이 값을 associated value라고 부른다. associated value 의 타입과 개수는 케이스 별로 각각 상이하게 정의되어도 된다.

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}


// 아래와 같이 사용
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
productBarcode = .qrCode // 컴파일에러

// <추가설명>
let qrCodeBarcode = Barcode.qrCode
// 이 상수의 타입은 (String) -> Barcode
let myQrCode = qrCodeBarcode("ABCDEFGHIJKLMNOP")
// 이 상수의 타입은 Barcode이고 값은 qrCode("ABCDEFGHIJKLMNOP")


// let과 var로 associated value를 추출할 수 있다
switch productBarcode {
case .upc(let numberSystem, let manufacturer, 
          let product, let check):
    print("UPC : \(numberSystem), \(manufacturer), 
           \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."


// 튜플 자체를 한번에 let 또는 var로 받아도 된다
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer),
          \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."
// <참고> 만약 case let .upc(values): 로 받으면
// values는 4개 요소로 구성된 튜플이다



Raw Values

C에서 했던 것처럼 Default value(raw value)로 ENUM의 케이스를 채울 수도 있다. 이 경우에는 모든 케이스의 값의 타입이 일치해야 한다.

enum ASCIIControlCharacter: Character {
    // raw value의 type을 필수로 명시해주어야 한다!
    // 여기서는 Character 라고 정의
    
    case Tab = "\t"
    case LineFeed = "\n" // raw value의 타입은 모든 케이스가 같아야
    case CarriageReturn = "\r" // 그리고 케이스 별로 유니크해야
}

print(ASCIIControlCharacter.Tab.rawValue) // "\t"

raw value는 꼭 Integer 값이 아니라 String, Character, Integer, Floating-Point 등 여러 타입을 사용할 수 있다.

<note> Raw Value는 ENUM을 처음 정의한 순간부터 들어가있는 디폴트 값이고 Associated Value는 ENUM의 케이스를 가지고 변수/상수를 만들 때마다 셋팅이 되는 값이다. 이 두개는 다른 것이다.

<참고> enum 선언 시 rawValue의 타입 지정을 하지 않은 경우에는 케이스에 대고 rawValue를 부르지 못한다. 그런 멤버가 없다는 오류가 난다.


<Implicitly Assigned Raw Values>

명시적으로 전부 raw value를 할당하지 않더라도, raw value의 type만 명시해준다면 컴파일러가 알아서 raw value를 할당해준다.

enum Planet: Int {
    case mercury, venus, earth, mars, jupiter
   // 0, 1, 2, 3, 4
}

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter
   // 1, 2, 3, 4, 5
}

enum Planet: Int {
    case mercury=1, venus, earth, mars=10, jupiter
    // 1, 2, 3, 10, 11
}

enum Planet: Int {
    case mercury, venus = 6, earth, mars, jupiter
   // 0, 6, 7, 8, 9
}

enum Planet: Int {
    case mercury, venus = 0, earth, mars, jupiter
    // 컴파일 에러 (raw value가 유니크하지 않다며)
    // 0, 0, 1, 2, 3 이 되니까
}

enum Planet: Int {
    case mercury = 6, venus = 5, earth, mars, jupiter
    // 컴파일 에러 (raw value가 유니크하지 않다며)
    // 6, 5, 6, 7, 8, ... 이 되니까
}


raw value 타입이 String 인 경우에는 default Raw Value 값이 Case 이름 자체이다.

enum CompassPoint: String {
    case north, south, east, west
}

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"


// <추가설명>
enum CompassPoint: String {
    case north, south = "north", east, west
    // raw value가 unique하지 않기 때문에 컴파일 에러
}


<Initializing from a Raw Value>

rawValue가 있는 ENUM은 rawValue를 파라미터로 넘기는 이니셜라이저를 사용할 수 있다.

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 의 타입은 Planet?

// rawValue가 7인 Type이 정의되어 있지 않다면 nil이 반환된다
// 이렇게 nil이 될 수 있는 이니셜라이저를 failable initializer 라고 한다

//  옵셔널 바인딩을 함께 활용
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// prints "There isn't a planet at position 11"



Recursive Enumerations

recursive enumeration 이란 한 개 이상 케이스의 associated value로 ENUM의 인스턴스를 받는 ENUM을 말한다. indirect 라는 키워드를 사용하여 만든다.

 // 다음과 같이 필요 케이스 앞에 indirect 키워드를 붙여도 되고
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 이렇게 enum 옆에 indirect 를 명시하면 모든 case에 적용
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}


이것을 이용하는 계산기 예제를 보자.

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

// evaluate (5 + 4) * 2
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, 
            ArithmeticExpression.Number(2))

print(evaluate(product))
// prints "18"


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

Properties  (0) 2017.02.27
Classes and Structures  (0) 2017.02.27
Closures  (0) 2017.02.21
Functions  (0) 2017.02.19
Control Flow  (0) 2017.02.15
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함