Swift/Swift 공식문서

[Swift] 공식문서 08 - Enumerations (열거형)

말차프라푸치노 2021. 10. 14. 23:05

 

 

 

Enumerations — The Swift Programming Language (Swift 5.5)

Enumerations An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code. If you are familiar with C, you will know that C enumerations assign related names to a set of in

docs.swift.org

1차 수정 (2022 / 02 / 26) : 의역 및 오타, 스타일 수정

 

 

Enumerations (열거형)

열거형은 값들의 그룹에 대해 공통의 타입을 정의해 코드 내에서 이러한 값을 안전하게(type-safe) 사용할 수 있도록 합니다.

 

기존의 C 언어 열거형은 정수 값에 관련된 이름을 할당합니다. Swift의 열거형은 더 유연하고, 각각의 열거형의 케이스에 대한 값을 제공할 필요가 없습니다.  만약 값이 각각의 열거형에 할당이 되면, 값은 string, character, int, float 타입을 가질 수 있습니다.

 

또 열거형 케이스(case)는 각각의 다른 케이스과 함께 저장될 모든 타입의 관련 값을 지정할 수 있습니다. 

 

Swift의 열거형은 그 자체로 firts-class 타입입니다. 열거형은 클래스에서만 지원되는 전통적인 특징을 수용합니다. 예를 들어 열거형의 현재 값에 대한 축사 정보를 제공하기 위해 계산된 프로퍼티들과 열거형이 현재 나타내는 값에 대한 기능을 제공하는 인스턴스 메서드가 있습니다. 열거형은 초기값(default)을 제공하기 위해 이니셜라이져(initizlizers = 생성자) 를 제공할 수 있고, 원래 구현된 거 이상으로 기능적으로 확장할 수 있으며, 표준기능을 제공하는 프로토콜을 준수할 수 있습니다.

 

 

 

Enumerations Syntax(열거형 구문)

열거형은 enum 키워드와 {} 를 사용해서 구현합니다.

enum SomeEnumeration {
    // enumeration definition goes here
}
enum CompassPoint {
    case north
    case south
    case east
    case west
}

위의 예제는 동서남북 방향을 나타냅니다. 각각의 방향은 열거형 케이스(enumertaion cases) 입니다. case 키워드를 사용해 열거형 케이스를 나타냅니다.

 

Swift의 열거형 케이스는 C 와 objective-C와는 다르게 기본적으으로 정수형 값을 가지지 않습니다. 위 예제의 north, south, east, west는 0,1,2,3으로 명시적으로 표현되지 않습니다. 

 

 

여러가지 케이스를 , 를 이용해 한 줄에 나타낼 수 있습니다.

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

 

각각의 열거형은 새로운 타입을 정의합니다. Swift의 다른 타입들과 마찬가지로 이름의 첫글자는 대문자로 작성합니다. 열거형은 복수의 이름보다는 단수 이름으로 작성합니다.

 

var directionToHead = CompassPoint.west

예제의 directionToHead의 타입은 CompassPoint의 값 중 하나가 초기화 될 때 추론됩니다. directionToHead가 CompassPoint의 타입으로 선언되면, CompassPoint의 다른 값들을 더 짧게 작성할 수 있습니다.

 

directionToHead = .east

directionToHead의 타입을 이미 알고 있으므로 CompassPoint를 생략해서 작성할 수 있습니다. 이렇게 작성하면 명시적으로 지정된 열거형 값에 대해 더 높은 가독성을 제공합니다.

 

 

 

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"

위 예제는 방향에 따라 스위치 case 문이 작성되었습니다.

 

Control Flow에서 설명한 것처럼, 스위치문은 열거형의 케이스를 작성할 때 완벽해야 합니다. 만약 case .west: 가 생략되었으면, 코드가 컴파일 되지 않습니다. 왜냐하면 CompassPoint 케이스의 목록을 다 사용하지 않았기 때문입니다. 철저한 보장을 요구함으로써 열거형 케이스가 실수로 생략되지 않게 해줍니다.

 

열거형의 모든 케이스를 다루는 것이 어려울 경우 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(열거형 케이스에 대한 반복)

몇 몇의 열거형에 대해, 열거형의 모든 케이스를 수집하는 것이 유용하곤 합니다. 열거형의 이름 뒤에 :CaseIterable을 작성할 수 있습니다. Swift는 모든 case를 열거형 타입의 allCases 프로퍼티로 표시합니다.

 

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

예제에서 Beverage.allCases를 사용함으로써 Beverage 열거형의 모든 케이스들의 컬렉션에 접근할 수 있습니다. 다른 컬렉션들처럼 allCases 를 사용할 수 있습니다. 컬렉션의 요소는 열거형 타입의 인스턴스입니다. 이 예제에서는 Beverage 값 들 입니다. 

 

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

위 예제는 for-in 루프를 사용해 케이스들을 반복했습니다. 위의 예제에 사용된 구문은 열거형이 CaseItable 프로토콜을 준수하는 것으로 표시합니다.

 

 

 

 

 

Associated Values(연관된 값)

앞에서 설명한 예들은 열거형의 케이스가 어떻게 그 자체로 정의된 값인 보여줍니다. 그러나 경우에 따라 이러한 케이스 값과 함께 다른 타입의 값을 저장할 수 있는 것이 유용합니다. 이 추가적인 정보를 associated value 라고 하며 해당 케이스를 코드에서 값으로 사용할 때 마다 달라집니다.

 

Swift 열거형은 정의하여 지정된 타입의 associated value를 저장할 수 있으며, 필요한 경우 열거형의 각 케이스에 대해 값의 타입이 다를 수 있습니다.  

 

예를들어 1D 바코드 -UPC 형식과 2D 바코드 - QR 코드가 있습니다. 1D 바코드는 0~9 사이의 숫자 1개 + 5개의 제조 숫자 + 5개의 제품 숫자 + 마지막 0~9 사이 숫자 1개로 구성되어있습니다.

출처 : Swift 공식 문서

2D 바코드는 최대, 2953글자의 문자열을 표현합니다.

출처 : Swift 공식 문서

 

스위프트에서 이 두가지 경우를 열거형으로 다음과 같이 정의합니다.

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

두 개의 케이스는 실제 Int 값과 String 값을 제공하지는 않습니다. 단지 바코드 상수 혹은 변수가 Barcode.upc 또는 Barcode.qrCode와 같을 때 저장할 수 있는 associated value의 타입만 정의합니다.

 

두 가지 유형 중 하나를 선택하여 새로운 바코드를 만들 수 있습니다.

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

associated tuple value로 할당이 됩니다.

 

같은 제품을 다른 타입의 바코드로 할당할 수도 있습니다.

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

이 경우 기존의 정수 값을 가지고 있던 Barcode.upc 에서 문자열 값을 가지고 있는 Bacode.qrCode로 대체됩니다. 상수 혹은 변수 탑의 바코드는 .upc와 .qrCode를 저장할 수 있습니다. 단 두 타입 중 하나만 가능합니다.

 

스위치 문을 사용하여 바코드 타입을 확인할 수 있습니다. 하지만 이 경우에는 associated value 가 스위치의 일부로 추출됩니다. 각각의 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."

 

만약 열거형 케이스에 대해 모든 associated value가 모두 똑같이 변수나 상수를 사용하면 앞으로 추출할 수 있습니다.

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

 

 

 

 

 

Raw Values(원시 값 = 초기 값)

앞서 다룬 바코드 예제에서 어떻게 열겨형의 케이스가 다른 타입의 associated value를 저장하는지 보았습니다. associated value를 대신해, 열거형 케이스는 모두 동일한 타입의 기본 값(default) 으로 미리 채워질 수 있습니다.

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

위의 예제에서 ASCIIControlCharacter라는 열거형의 원시 값은 문자(character) 타입으로 정의되며, 일반적이 ASCII 제어 문자로 설정됩니다.

 

원시 값은 문자열(String), 문자(Character), 정수형(Integer) 혹은 실수형(Float) 어떤 것이든 될 수 있습니다. 각각의 원시값은 해당 열거형 선언에서 고유(unique)한 값을 가져야 합니다.

 

원시 값은 associated value와 동일하지 않습니다. 위 예제의 세 개의 ASCII 코드와 같이 코드에서 열거를 처음 정의할 때 원시 값은 미리 채워진 값으로 설정됩니다. 특정 열거 사례의 원시 값은 항상 동일합니다. 연관된 값은 열거형의 사례 중 하나를 기반으로 새 상수 또는 변수를 만들 때 설정되며, 만들 때마다 다를 수 있습니다.

 

 

Implicitly Assigned Raw Values(암시적으로 할당된 원시 값)

정수(Integer) 혹은 문자열(String) 원시값을 저장한 열거형을 사용할 때, 각 각의 케이스에 대해 명시적으로 원시값을 할당할 필요가 없습니다. Swift가 자동으로 값을 할당합니다.

 

예를 들어, 원시 값에 정수를 사용할 경우, 각 케이스의 암시된 값은 이전 케이스보다 하나큽니다. 만약 첫 번째 케이스가 값을 할당받지 않으면, 그 값은 0 입니다.

 

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

위 예제는 태양계의 행성을 나타낸 열거형입니다. 예제에서 mercury(금성) 는 명시적으로 1의 원시 값을 가지고, venus(수성)는 암시적으로 2의 원시값을 갖습니다.

 

만약 문자열에 원시 값을 사용하면, 각 각의 케이스의 암시된 값은 케이스 이름의 텍스트 입니다.

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

위의 예제에서 CompassPoint.south는 암시적으로 "south"라는 원시 값을 가집니다.

 

rawValue 프로퍼티를 사용해 열거형 케이스의 원시값에 접근할 수 있습니다.

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

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

 

 

Initializing from a Raw value(원시 값에서 초기화)

만약 원시값(raw-value) 타입으로 열거형을 정의했다면, 열거형은 자동적으로 원시 값과 같은 타입의 값을 갖는 이니셜라이저를 받고, 열거형 케이스 혹은 nil 중 하나를 반환합니다. 열거형의 새로운 인스턴스를 만들기 위해 이 이니셜라이저를 사용할 수 있습니다.

 

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

위 예제는 원시값 7을 갖는 Uranus(천왕성) 특정합니다. 항상 모든 Int 값이 일치하는 행성을 찾는 것은 아닙니다. 이로 인해 원시값 이니셜라이져는 항상 옵셔널 열거형 케이스를 리턴합니다. 위 예제에서 possiblePlanet의 타입은 Planet? 혹은 `optional Planet` 입니다.

 

원시 값 이니셜라이져는 실패 가능한(failable) 이니셜라이져 입니다. 모든 원시 값이 열겨 케이스를 반환하지 않기 때문입니다.

 

만약 11번 째 위치의 행성을 찾으려고 하면, 원시값 이니셜라이져에 의해 반환된 옵셔널 Planet 값은 nil 일 것입니다. 

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 원시값을 가지는 행성에 접근하려고 합니다. 하지만 11번째 행성은 반환되지 않고 else 구문이 실행됩니다.

 

 

 

 

Recursive Enumerations(재귀 열거형)

재귀 열거형(Recursive Enumerations) 은 하나 혹은 그 이상의 열거형 케이스에 대한 associated value를 다른 열거형의 인스턴스로 가지는 열거형입니다. indirect 키워드를 열거형 case 앞에 작성함으로써 사용할 수 있습니다. indirect 키워드는 컴파일러에게 필수적인 간접참조 레이어 (layer of indirection)를 삽입하라고 명령합니다.

 

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

위 예제는 간단한 수학 표현식을 저장하는 열거형입니다.

 

열겨형 enum 앞에 indirect를 사용하여 associated value가 있는 모든 열거형의 케이스에 대해 indirection을 활성화 할 수 있습니다.

 

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

위 예제에서 열거형은 3가지 타입의 수학 표현식을 저장합니다. 그냥 숫자, 두 표현식의 합, 두 표현식의 곱.

addition 과 multiplication 케이스는 associated value를 가집니다. 이 associated value 또한 수학 표현식입니다. 이것을 통해 associated value가 중첩된 표현식을 만들 수 있다는 것을 알 수 있습니다. 

 

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

예제는 (5 + 4) * 2를 나타냅니다.

데이터가 중첩되기 때문에 데이터를 저장하는 데 사용되는 열거형도 중첩을 지원해야 합니다. 즉 열거형이 재귀적(recursive) 이여야 합니다.

 

재귀 함수는 재귀 구조를 가진 데이터를 사용하는 간단한 방법입니다.

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"

위 예제는 산술식을 평가하는 함수입니다. 이 함수는 associated value를 리턴함으로써 숫자를 평가합니다. 또한 표현식의 left 와 right를 평가함으로써 addition 과 multiplication을 평가합니다.