[Swift] 8. Enumerations(열거형)

도윤·2021년 7월 19일
0

Swift

목록 보기
8/21

Enumeration Syntax

enum 키워드 사용하여 선언

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

north,south,east,west가 enumeration case이다. 새로운 항목들을 더 추가하려면 case를 통해 하면 된다.


enum Planet{
	case mercury,venus,earth,mars,jupiter,saturn,uranus,neptune
}

같은 케이스들을 한 줄에도 나열할 수 있다.

열거형을 만들게 되면 하나의 새로운 타입처럼 사용할 수 있다. 그래서 열거형은 swift의 이름 규칙에 따라 이름을 대문자로 시작해야 한다.

var directionToHead = CompassPoint.west

위의 코드와 같이 열거형의 하나의 케이스에도 접근할 수 있다. 이제 directionToHead는 선언되어 CompassPoint type이므로 다음과 같이 수정도 가능.

directionToHead = .east

Matching Enumeration Values With a Switch Statement

switch와 함께 사용할 수 있다.

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

directionToHead는 열거형의 .south 케이스이다. 따라서 switch에서 case와 동일한 조건인 경우 명령을 수행한다.
switch구문의 케이스가 CompassPoint 걸형의 케이스를 모두 포함하기 때문에 default는 사용하지 않아도 되고, 만약 하나라도 빼먹으면 아래와 같이 default를 써야한다.

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Mostly harmless")
default:
    print("Not a safe place for humans")
}
// Prints "Mostly harmless

Iterating over Enumeration Cases

열거형을 사용할때 모든 케이스에 바로 접근가능하다면 편리할 것이다.
그래서 swift의 열거형은 이러한 기능을 제공하는데, CaseLterable프로토콜을 이용하여 사용할 수 있다.

enum Beverage: CaseIterable {
	case coffee,tea,juice
}

let numberOfChoice = Beverage.allCases.count
print("\(numberOfChoice) beverages availabe")
// Prints "3 beverages available"

CaseIterable 프로토콜을 채택하고, allCases 프로퍼티를 사용하면 케이스들이 Array로 반환하게 된다.

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

Associated Values

앞의 모든 예는 모두 열거형의 케이스가 명시적으로 정의되는 것이었다. 이렇게 정의된 열거형을 상수나 변수에 Planet.earth와 같이 저장하고 나중에 이 값을 확인했다. 하지만 가끔은 다른 타입의 값과 열거형의 케이스
저장하는 것이 유용할 수도 있다. 이렇게 열거형의 케이스와 저장되는 값이 다른 값을 Associated Value라고 한다.

어떤 타입의 값도 열거형과 함께 사용할 수 있고 각각의 열거 케이스마다 다른 타입을 사용할 수 있다.
이러한 열거형은 다른 프로그래밍 언어에서 discriminated unions,tagged unions,variant라고 알려진 것들과 유사하다.

예를 들어 재고를 관리하는 시스템이 서로 다른 두 ㅌ타입의 바코드로 재고를 관리한다고 생각해보겠다.
일부 제품에는 UPC 형태의 1D바코드가 있고 0~9까지의 숫자를 사용하여 제조업체 코드 숫자와 제품 코드 숫자를 낱타낸다고 가정해보겠습니다.

다른 제품은 QR코드의 형ㅌ태로 2D바코드로 구성되어 있고 ISO 8859-1 문자를 사용하고 최대 길이가 2,953까지 가능하다.

즉 두가지 타입의 케이스를 열거형에서 한번에 다룰 수 있다.


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

upc바코드 케이스는 Int타입을 4개 사용하는 튜플형태로, QR코드 케이스는 String타입으로 열거 케이스를 나눠줄 수 있다. 이렇게 해서 각각의 탙입마다 새로운 값을 생성할 수 있다.

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

이렇게 되면 productBarcode 변수에는 하나의 값만 저장할 수 있게 된다. 즉 upc 바코드 탙입이나 QR코드 타입 중 하나만 저장할 수 있다.


switch productBarcode {
	case .upc(let numberSystem, let manefacturer, let product,let check) :
    	print("UPC:\(numberSystem),\(manufacturer),\(product),\(check)")
	case .qrCode(let productCode) {
    	print("QR code : \(productCode).")
}
// Prints "QR code : ABCDEFGHIJKLMNOP"

즉 위와 같이 switch와 함께 사용해서 해당 값에 있는 값을 추출할 수 있다. 또한 여러값이 들어있는 associated value를 위해 값들마다 변수 혹은 상수로 정해 이름을 붙여 사용할 수 있다.


Raw Value

위에 본 Associated Value가 여러 열거형 타입으로 이뤄진 값을 저장한 방법이었다면,raw value에는 default값을 미리 선언하는 것이다.

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Character 타입으로 정의되었지만 뿐만 아니라 Int,Float,Double,String타입이 될 수도 있다.
각 케이스마다 모두 고유한 값이어야 한다.

Raw Value는 Associated와는 다르다. Row는 열거형을 처음 정의할 때 미리 default값을 선언하는 것이고 Associated Value는 타입만 정해두고 나중에 채우는 것이다. 즉 Raw Value는 새로운 인스턴스가 모두 같은 값이지만 Associated Value는 선언하는 대로 변할 수 있다.

implicitly Assigned Raw Value

Int,String으로 열거형의 Raw Value를 선언하면 모든 케이스에 대해 Raw Value를 선언하지 않아도 된다. 물론 선언하면 그 값이 해당 케이스의 raw value가 되지만 선언하지 않으면 자동으로 할당된다.

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

raw value를 mercury에만 1이라고 선언했지만 swift가 알아서 venus에는 2, earth에는 3으로 할당했다.

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

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

위 처럼 raw value를 선언하지 않으면 케이스의 이름이 String타입으로 Raw Value가 된다.

Initializing from a Raw Value

열거형의 새로운 인스턴스를 생성할 때, 변수나 상수에 rawValue라는 매개변수로 초기화할 수 있는데 이렇게 되면 반환값으로 해당 raw case나 nil값이 반환된다.

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

rawValue가 7인 uranus를 반환하지만 없다면 nil이 반환되어서 Optional Binding을 통해 안전하게 호출해야 한다.

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

위 처럼 11에 해당하는 값은 존재하지 않기때문에 else문이 실행된다.


Recursive Enumberations

재귀 열거형으로 어떤 열거형의 케이스에 Associated Value의 타입으로 자신의 열거형 타입이 들어간 경우를 말한다. indirect 키워드를 사용해야 한다

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

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"

0개의 댓글