개발자의 삽질
[Swift] Enumerations 에 대해 알아보자 본문
https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html
오늘은 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가지 타입의 바코드 이다.
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 에 대해 알아보았다!
'Swift' 카테고리의 다른 글
[Swift] Structure vs Class 무엇을 골라야 할까? (0) | 2022.01.14 |
---|---|
[Swift] Queues & Threads - Concurrency by Tutorials 2편 (0) | 2022.01.13 |
[Swift] Inheritance (1) | 2022.01.12 |
[Swift] GCD & Operations - Concurrency by Tutorials 1편 (0) | 2021.12.20 |
[Swift] ARC 에 대해서 알아보자 (0) | 2021.12.16 |