서로 연관된 값들을 하나의 그룹으로 묶어 타입으로 만들 때 열거형을 쓴다.
열거형은 일급 객체이다.
전통적으로 클래스에서만 가능했던 계산 속성, 인스턴스 메서드, 이니셜라이저, 확장과 프로토콜도 사용 가능하다.
enum CompassPoint {
case north
case south
case east
case west
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
이렇게 case를 각각 쓸 수도 있고, 쉽표로 연결하여 쓸 수도 있다.
첫글자는 대문자로, 이름은 단수형으로 짓는다.
var directionToHead = CompassPoint.west
directionToHead = .east
처음에 변수가 CompassPoint.west 로 초기화되고 나서 사용시에는 타입명을 생략해도 된다.
열거형은 Switch문과 궁합이 좋아 자주 같이 쓰인다.
열거형은 한정된 사례(케이스)로 만든 타입이고, 스위치문은 표현식에 대한 분기처리에 최적화되어있기 때문이다.
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문은 Enum의 case들을 고려할 때 포괄적이어야 한다.
포괄적이라는 뜻은 모든 case를 포함해야 한다는 것이다. 한가지라도 빠지면 컴파일조차 안된다.
이런 제약사항때문에 switch문을 쓰게 되면 모든 케이스가 빠짐없이 있다는 사실을 보장한다.
만약 switch문에서 모든 열거형 케이스를 다 적어주지 못한다면 default를 사용해보자.
명시해주지 않은 케이스들을 한곳에 담아두게 된다.
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless"
열거형의 모든 케이스들을 담은 컬렉션이 필요할 때
CaseIterable 키워드를 붙이면 (CaseIterable 프로토콜을 채택하여 그 안의 allCases 프로퍼티를 사용하는구나)
1. 모든 케이스를 담은 컬렉션이 제공되고
2. 그 컬렉션에 접근할 수 있도록 .allCases 프로퍼티가 제공된다.
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
열거형의 인스턴스들이 컬렉션에 들어가서 for-in 문으로 하나씩 꺼내올 수 있다.
case가 카테고리의 역할로 바뀐다.
원시값과의 다른점?
원시값은 열거형을 선언할 때 구체적인 실제 정수형 "값"을 담지만, 연관값의 경우에는 그저 선언시 타입만 지정해주어서 값을 생성하는 시점에서 구체적인 값을 저장하게 된다.
각각의 케이스에는 다른 value 타입을 지정해줄 수 있다.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
두가지 종류의 바코드를 열거형으로 선언했는데, 각자 다른 연관값을 담는다.
선언시에는 구체적인 정보를 담지 않고 실제 사용시에 담는다.
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
실제 바코드를 생성해보았다.
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
같은 변수에 다른 타입의 바코드를 담아볼 수도 있다.
Barcode 타입의 변수는 .upc나 .qrCode 형태를 담을 수 있지만, 한번에 하나의 형태만 담을 수 있다.
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."
모든 연관값이 상수로 추출되는 경우, 각각 연관값 앞에 써주는 것이 아니라 맨 앞에 한번만 사용가능
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
Int
나 String
타입을 원시값으로 주로 사용한다.Int
나 String
타입을 원시값으로 사용시, 각각의 케이스에 직접 값을 할당해주지 않아도 된다.
정수형의 경우 아무것도 할당되어있지 않을 때, 첫번째 케이스에 0이 할당되고 +1씩 되어 할당된다.
문자열의 경우에는 타입 지정 후 원시값을 할당하지 않는다면 그 케이스 자체가 원시값이 된다.
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
enum CompassPoint: String {
case north, south, east, west
}
let earthsOrder = Planet.earth.rawValue //3
let sunsetDirection = CompassPoint.west.rawValue //"west"