Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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 31
Archives
Today
Total
관리 메뉴

개발자의 삽질

[Swift] Enumerations 에 대해 알아보자 본문

Swift

[Swift] Enumerations 에 대해 알아보자

uniqueimaginate 2022. 1. 1. 00:26

https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html

 

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


오늘은 Enumerations 에 대해 알아보자

  • Enumeration 은 서로 연관된 값을 가지는 그룹의 일반 타입을 정의하고 이러한 값들이 타입 세이프한 방법으로 동작하게 한다.
  • Enumeration 에 각각의 case 에 있는 값은 문자열, 문자, 정수 또는 부동 소수 타입을 가질 수 있다.

사실 더 많은 설명이 있으나, 예시 없이 이해가 어렵기 때문에 글을 진행하면서 이해하는 것이 나을 것 같다.

Enumeration Syntax

enum 키워드를 이용해 선언한다.

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

case 키워드를 통해 enum 안에서 각각의 case 를 선언한다.

여기서 north, south, east, west 는 0, 1, 2, 3 이 아니다.

각각의 서로 다른 case 는 CompassPoint 의 명시적으로 정의된 타입으로 자체 값이다.

 

아래와 같이 여러 개의 case 들을 콤마로 분리할 수도 있다.

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

각각의 enumeration 은 새로운 타입으로 정의된다. 그 타입은 대문자로 시작한다.

enumeration 타입에는 복수형이 아닌 단수형으로 이름을 짓는다.

var directionToHead = CompassPoint.west

directionToHead 의 타입은 CompassPoint 의 값으로 초기화 될 때 타입이 추론된다.

directionToHead 가 CompassPoint 로 선언되었다면, dot syntax 를 이용해 CompassPoint 의 다른 값으로 설정할 수 있다.

directionToHead = .east

Matchint Enumeration Values with a Switch Statement

Switch 문을 이용해서 각각의 enumeration 값을 매칭할 수 있다.

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"

이 때 주의할 점은, switch 문은 열거형 케이스를 고려할 때 항상 모든 케이스를 담아야 한다.

여기서 .west 케이스가 생략된다면 이 코드는 컴파일 되지 않는다.

모든 케이스를 고려하고 싶지 않다면 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

몇몇의 enumeration 는 모든 enumeration 케이스들의 컬렉션을 갖는 것이 유용하다. 

: CaseIterable 을 enumeration 이름 뒤에 작성한다면 이러한 기능을 사용할 수 있다.

Swift는 allCases 프로퍼티를 통해 모든 케이스에 접근할 수 있게 한다. 

CaseIterable 프로토콜: https://developer.apple.com/documentation/swift/caseiterable

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 (연관된 값)

지금까지는 각각의 케이스들은 그 자체로 자신의 타입, 값을 선언하고 있다.

그러나 때로는 케이스와 관련된 값으로 값을 설정하는 것이 유용할 때가 있다.

이렇게 추가적인 정보를 저장하는 것을 associated value 라고 한다.

 

당신은 Swift enumerations 에 어떤 타입의 어떤 값도 담을 수 있다. 

또한 필요하다면 서로 다른 케이스에 서로 다른 타입의 값을 저장 할 수도 있다.

 

예를 하나 들어보자

재고 추적 시스템에는 2가지 타입의 바코드로 제품을 추적한다.

다음은 2가지 타입의 바코드 이다.

Barcode UPC(left) / Barcode QR(right)

Swift 는 2타입의 바코드를 다음의 enumeration 으로 정의한다.

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

위의 코드는

"(Int, Int, Int, Int) type 라는 연관된 값을 가지는 upc 값, 또는 String type 연관된 값을 가지는 qrCode 값을 갖는 Barcode enumeration 타입을 정의한다." 를 의미한다.

이 정의는 그 자체로 Int 나 String 값을 제공하지 않는다. 

단순히 Barcode 상수와 변수들의 연관된 값의 타입을 정의할 뿐이다.

따라서 다음과 같이 새로운 barcode 를 만들 수 있다.

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
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 로 되어있다면 아래와 같이 let, var 을 case 이름 앞으로 가져올 수 있다.

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

enumeration 은 모든 케이스가 같은 타입인 기본 값을 가질 수 있다. 이를 raw values 라고 한다.

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

raw values 는 String, Character, integer, floating-point number 타입이 될 수 있다.

각각의 raw value 는 유일한 값이어야 한다.

Implicitly Assigned Raw Values

string 이나 integer 타입의 raw values 를 갖는 enumeration 을 다룰 때는 명시적으로 값을 할당하지 않아도 된다.

할당하지 않았을 때, Swift 에서 알아서 값을 할당해준다.

예를 들어, raw values 가 integer 일 경우에, 각각의 case 의 값은 이전 case의 값보다 1이 크다.

만약 첫 번째 case 의 값을 지정하지 않는다면, 0 으로 시작한다.

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

위는 1부터 순차적으로 2,3,4 ... 이렇게 간다.

raw values 가 string 일 경우에는, 각각의 케이스 이름을 값으로 할당한다.

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

let directionPoint = CompassPoint.north.rawValue
// directionPoint is "north"

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

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

Iniitizling from a Raw Value

당신이 raw-value 타입을 이용해 enumeration 을 정의한다면, enumeration 은 자동으로 raw value 타입의 값을 인자로 받는 생성자를 갖게 되고 enumeration 의 케이스 또는 nil 값을 반환하게 된다.

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

위의 경우는 7 이라는 raw value 가 Uranus 을 찾을 수 있게 한다.

그러나 모든 숫자가 행성과 매칭되는 것은 아니다.

따라서 raw value 생성자는 언제나 optional 타입의 enumeration case 를 반환한다.

위의 경우에 possiblePlanet 의 타입은 Planet? 이 된다.

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"

위의 예시는 optional binding 을 이용해 else branch 로 가게 한다.

Recursive Enumerations

recursive enumeration 은 연관된 값으로 한 개 또는 그 이상의 enumeration 인스턴스를 갖고 있는 enumeration 이다.

indirect 키워드를 통해서 사용한다.

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

// 아래와 같이 indirect 키워드를 enum 앞에 적어도 된다.
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

아래는 recursive enumeration 사용을 보여주는 예시이다.

재귀의 특성을 잘 보여주고 있다.

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"
이번 글은 Enumeration 에 대해 알아보았다!
Comments