Swift study - Enumerations

rbw·2022년 3월 7일
0

swift-study

목록 보기
11/17
post-thumbnail

Swift - Enumerations

열거형은 관련있는 값들의 그룹을 일반 타입으로 정의하고, 코드 내부에서 타입-안전한 방식으로 값들과 함께 작업할 수 있도록 해준다.

C언어에 익숙하다면, C의 열거형이 관련된 이름들을 정수 값 집합에 할당하는 것을 알 것이다. 스위프트의 열거형은 좀 더 유연하고, 각 열거형 케이스에 대해 값들을 제공할 필요가 없다. 각각의 열거 케이스에 값(원시 값)이 제공 되었다면, 그 값은 문자열, 문자, 실수, 정수가 될 수 있다.

또는 열거형 케이스들은 유니온 또는 C++의 variant가 다른 언어에서 하는 것 처럼 각각의 다른 케이스 값과 함께 저장할 모든 타입의 관련 값들을 지정할 수 있다. 하나의 열거형 부분으로 관련된 공통 집합을 정의할 수 있고, 각각의 경우에는 연관된 적절한 타입의 다른 값의 집합을 가진다.

variant : union과 비슷하지만 타입에 안전하다.

스위프트는 열거형은 그 자체로 1급 타입이다. 일반적으로 클래스에 지원되는 많은 특징들을 채택한다. 예로는 열거형의 현재값에 대한 추가 정보를 제공하는 계산된 프로퍼티, 열거형이 나타내는 값과 관련된 기능을 제공하는 인스턴스 메소드가 있다. 열거형은 또한 초기값을 제공하기 위해 이니셜라이저를 제공 할 수 있다. 원래 구현을 넘어 기능을 확장하도록 확장할 수 있다. 그리고 기본 기능을 제공하기위해 프로토콜을 준수할 수 있다.

property : 값을 특정 클래스, 구조체, 열거형에 연결한다.

Enumeration Syntax (열거형 구문)

enum 키워드를 작성하여 열거형을 작성할 수 있다.

enum SomeEnumeration {
    // enumeration의 정의는 이곳에 작성한다
}

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

// 콤마를 사용하여 한줄로 나타내는 경우
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

열거형안의 정의된 값을 열거형 케이스라고 한다. case 키워드와 함께 새로운 열거형 케이스를 작성이 가능하다.

NOTE

스위프트의 열거형 케이스는 C언어와 Objective-C와는 다르게 기본적으로 정수 값을 가지지 않는다. 위의 예에서 열거형 케이스는 자체 값이며, 명시적으로 CompassPoint 라는 타입으로 정의된다.

각각의 열거형의 정의는 새로운 타입으로 정의된다. 스위프트의 다른 언어와 같이 해당 이름은 대문자로 시작한다. 열거형에 단수형 이름으로 지정하여 이해하기 쉽도록 한다.

var directionToHead = CompassPoint.west

// CompassPoint로 유추가 되므로, 다른 케이스의 값을 할당하는 것이 가능하다.
directionToHead = .east

directionToHead 타입은 이미 알고 있기 때문에, 값을 설정시 유형 빼고 작성이 가능하다. 이렇게 하는 것은 명시적으로 입력된 열거형 값으로 작업시에 코드의 가독성을 높인다.

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"

조건문 파트에서 설명 했듯이, 스위치문은 열거형의 케이스를 고려할 때 완전해야만 한다. 위 경우 .west가 생략되어있다면 코드는 컴파일 되지 않는다. CompassPoint 케이스의 전부를 고려하지 않기 때문이다. 이러한 철저함은 열거형 케이스를 실수로 빠트린것을 막아준다.

모든 케이스를 제공하지 않을 때 default를 사용해서 컴파일에러를 막을 수 있다.

Iterating over Enumeration Cases (열거형 케이스들의 반복)

일부 열거형의 경우, 열거형의 케이스들 전부를 수집하기 유용하다. : CaseIterable을 열거형 이름 뒤에 작성하여서 사용한다. 스위프트는 allCases라는 열거형 프로퍼티를 사용하여 모든 케이스의 컬렉션을 보여준다.

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

// for-in 루프에 사용하는 모습
for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice

컬렉션의 요소는 열거형 유형의 인스턴스이다. 위의 경우는 Beberage값이 된다. 위의 예에서 사용된 구문은 열거형을 CaseIterable 프로토콜을 준수하여 표시한다.

Associated Values (연관된 값들)

위의 예시들과 다르게, 케이스 값과 함께 다른 타입의 값을 저장하는것이 유용할 때가 있다. 이러한 추가 정보를 연관된 값이라고 부른다. 이 해당 케이스는 코드에서 값으로 사용될때마다 달라진다.

주어진 타입의 연관된 값을 저장하기 위해 스위프트 열거를 정의 할 수 있다. 필요하다면, 값의 타입은 열거형의 각 경우에서 매번 다를 수 있다.

바코드와, QR 코드의 예시 바코드는 4가지 숫자가 필요하다.

barcode

QR 코드는 문자열로 이루어진다.

QRcode

두 가지 타입의 바코드를 정의하는 코드

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

// 위 타입을 이용하여 바코드를 생성하는 모습
var productBarcode = Barcode.upc(8, 85909, 51226, 3)

// 타입과 값이 대체된다
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

위의 열거형의 정의는 실제 Int 값이나 String 값을 제공하지는 않는다. 이것은 단지 Barcode.upc or Barcode.qrCode와 같을 때 바코드의 상수 및 변수가 저장할 수 있는 연관된 값의 타입을 정의한다.

연관된 값은 스위치 문의 일부로 추출된다. 앞의 let, var를 붙여서 스위치문 내부에서 연관된 값을 추출한다.

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

Raw Values (원시 값)

연관된 값의 대안으로 모두 동일한 유형의 기본값으로 미리 채울 수 있다.

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

각 원시 값은 열거형 선언 내에서 고유해야 한다.

NOTE

원시값은 연관된 값과 같지않다. 원시 값은 코드에서 열거형을 처음 정의할 때 미리 채우는 값이다. 특정 열거 케이스의 원시 값은 항상 같다. 연관된 값은 열거형 케이스의 하나를 기초하여 새로운 상수나 변수를 생성할 때 설정되고 이는 사용시마다 달라질 수 있다.

Implicitly Assigned Raw Values (원시값의 암시적 할당)

정수나 문자열의 원시값을 저장하는 열거형으로 작업할 때, 각각의 케이스의 명시적으로 값을 할당할 필요는 없다. 그렇게 하지 않을 시, 스위프트는 자동으로 값을 할당해준다.

정수 원시값을 사용하는 경우, 각각의 암시적인 값은 이전 케이스보다 하나를 더한다. 만약 처음 케이스가 값이 없다면 이는 0 이다.

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// venus = 2, earth = 3, mars = 4, ...  로할당된다 

문자열 원시값을 사용할 때, 각 케이스의 암시적 값은 케이스의 이름을 따른다.

enum CompassPoint: String {
    case north, south, east, west
}
// CompassPoint.south = south, CompassPoint.north = north, ... 로 할당된다.

// rawValue 속성을 이용해서 접근이 가능하다.
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

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

Initializing from a Raw Value (원시값으로 초기화)

원시값과 함께 열거형을 정의 했다면, 열거형은 자동적으로 원시 값의 타입의 값을 취하는 이니셜라이저를 받고, 열거형의 케이스 또는 nil을 반환한다. 새로운 열거형의 인스턴스 생성을 위해 이니셜라이저를 사용할 수 있다.

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet타입은 Planet? 이며 Planet.uranus랑 같다

가능한 모든 정수 값이 행성을 찾는것은 아니다. 이러한 이유 때문에 원시값 이니셜라이저는 항상 옵셔널 열거형 케이스를 반환한다.

만약 11인 값으로 행성을 찾으려고 한다면, 원시값 이니셜라이저는 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"

Recursive Enumerations (재귀 열거형)

재귀 열거형은 열거형 케이스에 하나 이상의 연관된 값으로 열거형의 다른 인스턴스를 가지고 있는 열거형이다. 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)
}

위 열거형에는 세 가지의 산술 표현식이 저장될 수 있다. 숫자, 두 표현식의 덧셈, 두 표현식의 곱셈이다. 예를 들어, (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))

재귀 함수는 재귀 구조의 데이터와 작업하는 간단한 방법이다.

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"

위 함수는 간단하게 연관된 값을 반환하여 숫자를 평가한다. 이는 왼쪽 먼저 평가하여 덧셈 또는 곱셈을 평가하고, 그 다음 우변 식을 평가한다. 그러고 그것들을 더하거나 곱하여 덧셈 또는 곱셈인지를 평가한다.

profile
hi there 👋

0개의 댓글