본문 바로가기
Swift/Language Guide

Enumerations(열거형)

by diosmio 2021. 9. 16.

열거형은 어떤 값들을 group으로 묶어 경우의 수를 제한하는 타입이다

 

개념적으로는 C언어의 것과 유사하나

Swift에서는 각 case에 정수 외의 String/Float 등을, 심지어 여러 개도 할당할 수 있다는 차이가 있다

 

연관값(associated value)이라는 개념으로 case마다 다양한 타입과 여러 개의 값을 저장시킬 수 있다

 

Swift의 열거형은 계산 프로퍼티, 인스턴스 메소드, 생성자 등도 사용할 수 있다

 

익스텐션과 프로토콜도 적용할 수 있다

 

 

목차
1. 열거형의 구문
2. 열거형 + switch문 조합 시 유의사항
3. 모든 case 루프 돌리기
4. 연관값 (Associated Value)
5. Raw Value
6. 순환 열거형 (Recursive Enumerations)

 

 

 

열거형 구문

 

기본형태는 아래와 같다

enum CompassPoint {
    case north
    case south
    case east
    case west
}

//이어 쓸 수도 있다
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

나침반을 예시로 4가지 열거형 case를 선언하였다

 

주의

Swift에선 C언어와 달리, 각 case에 암시적으로 정수가 할당되지 않는다.

정수 대신 case의 이름 자체가 할당된다. (ex. north, south ...)

 

 

 

열거형 타입을 할당하는 예시

var directionToHead = CompassPoint.west

directionToHead = .east

 

한 번 타입이 설정되면 CompassPoint라는 타입명은 생략해도 된다

 

 

 

 

 

열거형 + switch문 조합

열거형 타입을 Switch의 조건값으로 줄 때는 case를 전부 명시하거나 default를 줘야한다

enum CompassPoint {
    case north
    case south
    case east
    case west
}

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"

 

 

 

 

 

열거형 case 루프 돌리기

다루고 있는 열거형 타입이 CaseIterable 프로토콜을 채택하게 함으로써 case 루프를 돌릴 수 있다

채택 후 .allCases 프로퍼티를 통해 case를 Collection 타입으로 뽑아낸다

 

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice

 

※ 블로그 출처라 정확하진 않지만,,, 프로토콜 채택만 하면 구현은 "컴파일러"가 자동으로 제공한다고 한다

 

 

 

 

 

연관값 (Associated Values)

 

정의

각 case에 값을 할당하는 2가지 방법 중 하나인 연관값이다.

 

연관값은 열거형 인스턴스마다 다른 값을 가지는, 일종의 인스턴스 프로퍼티처럼 생각할 수 있다.

 

또한, case마다 다양한 타입의 값을 심지어 여러 개를 할당할 수 있다

 

연관값의 타입은 튜플이며 내부 타입 구성은 서로 달라도 무관하다

 

 

예제 - 선언 & 할당

덕분에 이런 구현이 가능하다.

예로)
마트에서 상품들을 추적하는 시스템을 만드는데 바코드가 두 종류가 있는데
바코드의 종류에 따라 이를 번역하는 방법이 매우 다른 상황이다

[ UPC 타입 바코드 ]

[ QR코드 타입 바코드 ]

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

var productBarcode = Barcode.upc(8, 85909, 51226, 3)

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

 

연관값은 이름이 없고 타입만 명시해준다

 

 

예제 - 연관값 추출

switch문으로 추출할 수 있다

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


// 전부 상수 or 전부 변수로 추출하는 경우라면
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."

✅ 이 때, 연관값은 값타입으로 추출되므로

변수로 선언하여 값을 변경하더라도 원본은 바뀌지 않는다

 

 

 

 

 

 

Raw Values (=디폴트값)

정의

case에 값을 할당하는 두번째 방법인 Raw value이다

 

Raw value는 모든 인스턴스가 공유하는 일종의 타입 프로퍼티로 생각할 수 있고

런타임이 아닌 디폴트값으로만 설정할 수 있다

 

연관값과 달리, case마다 하나씩만 저장할 수 있고 모든 case가 같은 타입을 사용해야 한다

 

열거형 타입 이름 옆에 Raw value의 타입을 명시해주어야 한다

 

 

 

구문 간단하게 쓰기

#1. 첫번째 case에 1을 주면 나머지는 자동으로 2부터 할당된다

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

 

#2. 타입을 String으로 지정하면 case 이름 그대로 할당한다

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

 

 

case 이름이 아닌 Raw Value로 역추적하여 생성하기

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus

1대1 대응이라서 이런 생성자도 제공된다

 

참고로, rawValue에 해당하는 case가 없을 수 있으므로 Optional 생성자이다.

 

대응되는 case가 없는 예제를 하나보면,

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)

정의

자기자신 열거형 타입의 인스턴스를 연관값으로 갖는 열거형을 말한다.

 

indirect 키워드를 enum 앞에나 case 앞에 명시하여 순환 열거형 case임을 알린다

 

// 둘 다 가능
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 열거형 인스턴스가 마구 쌓인다..
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
// product에 사실상 3개 이상의 인스턴스가 담기게 되었다. 무제한 가능할듯

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

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

evaluate이라는 함수 하나로 Recursive 구조를 구현해버렸다

 

 

'Swift > Language Guide' 카테고리의 다른 글

Inheritance (상속)  (0) 2021.09.18
Subscripts  (0) 2021.09.17
Closures  (0) 2021.09.15
Error Handling - #1/1  (0) 2021.09.13
Access Control - #1/1  (0) 2021.09.13

댓글