요일이나 나침반 방향, 컨트롤러의 버튼처럼 정해진 몇 가지 상태 중 하나만 가질 수 있는 데이터를 모델링할 때는 Int나 String 같은 기본 타입을 쓰면 문제가 많아요.
정수나 문자열을 쓰면 코드에서 999 같은 잘못된 상태 코드나 "leftt" 같은 오타가 아닌지 끊임없이 확인해야 하고, 사람이 봐도 그 숫자가 무엇을 나타내는지 쉽게 알 수 없어요.
Swift는 이 문제를 열거형으로 해결해요(enum이라고 줄여 부르기도 해요). 열거형은 서로 관련된 이름 있는 값들의 공통 타입을 정의해요. 컴파일러는 가능한 모든 케이스를 미리 알고 있기 때문에, 컴파일 시점에 코드가 정확하고 빠짐없이 작성됐는지 검사할 수 있어요. 열거형은 메서드와 이니셜라이저, 연산 프로퍼티도 가질 수 있어요.
enum 키워드 뒤에 타입 이름을 쓰고, 중괄호 안에 케이스를 나열하면 열거형을 정의할 수 있어요.
Swift에서는 관례적으로 타입 이름에는 UpperCamelCase를, 각 케이스에는 lowerCamelCase를 사용해요.
enum NESButton {
case up
case down
case left
case right
case a
case b
case select
case start
}
여러 케이스를 쉼표로 구분해서 한 줄에 정의할 수도 있어요.
enum NESButton {
case up, down, left, right, a, b, select, start
}
열거형 케이스를 가리킬 때는 열거형 타입 이름, 점(.), 케이스 이름 순서로 써요. 타입을 이미 알고 있거나 컴파일러가 추론할 수 있으면 타입 이름을 생략하고 더 짧은 점 문법을 쓸 수 있어요.
var lastPressed = NESButton.up
// Swift infers the type, so you can omit the type name:
lastPressed = .down
let konamiCode: [NESButton] = [.up, .up, .down, .down, .left, .right, .left, .right, .b, .a]
열거형은 클래스나 구조체처럼 메서드와 사용자 정의 이니셜라이저를 정의할 수 있어요. 열거형 메서드 안에서 self는 현재 케이스 값을 가리켜요.
enum Coin {
case heads
case tails
init(_ value: Int) {
if value.isMultiple(of: 2) {
self = .heads
} else {
self = .tails
}
}
func flip() -> Coin {
switch self {
case .heads: return .tails
case .tails: return .heads
}
}
}
let tails = Coin(13) // .tails
let heads = Coin(0) // .heads
let flipped = heads.flip() // .tails
열거형 케이스에는 원시값이라고 부르는 기본값을 미리 지정할 수 있어요.
한 열거형 안의 모든 원시값은 같은 타입(Int, String, Character 등)이어야 해요.
enum NESButton: Character {
case up = "⬆️"
case down = "⬇️"
case left = "⬅️"
case right = "➡️"
case a = "🅰️"
case b = "🅱️"
case select = "✅"
case start = "🚦"
}
print(NESButton.left.rawValue) // Prints "⬅️"
init(rawValue:)를 사용하면 원시값으로 열거형을 초기화할 수 있어요. 유효하지 않은 원시값이 들어올 수도 있기 때문에, 이 이니셜라이저는 옵셔널을 반환해요.
let upButton = NESButton(rawValue: "⬆️") // Optional(NESButton.up)
let invalid = NESButton(rawValue: "🙄") // nil
Swift는 String과 Int 열거형의 원시값을 자동으로 지정할 수 있어요.
String 열거형에서는 각 케이스의 원시값이 기본적으로 케이스 이름이 돼요.Int 열거형에서는 별도로 지정하지 않으면 원시값이 0부터 시작해서 1씩 증가해요.enum Planet: Int {
case mercury = 1, venus, earth, mars
}
print(Planet.earth.rawValue) // Prints 3
이 연습 문제에서는 로그 라인을 처리해요.
각 로그 라인은 다음과 같은 형식의 문자열이에요: "[<LVL>]: <MESSAGE>".
로그 레벨은 다음과 같아요:
TRC (추적)DBG (디버그)INF (정보)WRN (경고)ERR (오류)FTL (치명적)필요한 기능을 모두 갖추려면 세 가지 작업을 각각 완료해야 해요.
위의 로그 레벨에 해당하는 여섯 개의 케이스와, 로그 레벨이 없거나 표준이 아닌 경우를 위한 unknown 케이스를 가진 LogLevel 열거형을 정의해요.
tracedebuginfowarningerrorfatalunknown다음으로, 로그 라인의 로그 레벨을 파싱하는 LogLevel 이니셜라이저를 구현해요:
LogLevel("[INF]: File deleted")
// Returns LogLevel.info
LogLevel("Something went wrong!")
// Returns LogLevel.unknown
로그 라인의 로그 레벨은 꽤 길어요.
로그 라인을 저장하는 데 필요한 디스크 공간을 줄이기 위해 짧은 형식을 만들었어요: "<ENCODED_LEVEL>:<MESSAGE>".
인코딩된 로그 레벨은 로그 레벨을 숫자로 단순하게 매핑한 것이에요:
trace - 0
debug - 1
info - 4
warning - 5
error - 6
fatal - 7
unknown - 42
짧아진 로그 라인 형식을 String으로 출력하는 shortFormat(message: String) -> String 메서드를 구현해요:
let overflow = LogLevel.error
overflow.shortFormat(message: "Stack overflow")
// Returns "6:Stack overflow"
Exercism에 가입하고 Swift 트랙을 개념 35개연습 문제 116개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.