Enumeration 은 C에서도 쓰이던 문법이다.
내가 원하는 값들을 묶어서 새로운 자료형에 집어넣는 거!
근데 C에서는 값들을 정수에 묶었는데, Swift는 훨씬 유연하게 내가 원하는 자료형에다 묶을 수 있음!심지어 각 케이스에다가 값을 꼭 할당하지 않아도 된다.
또 이건 first-class type(1급 객체) 라서 일반 자료형에 쓰이는 모든 연산을 써먹을 수가 있다!
enum EnumName{
case one
case two
case three, four, five, six
}
뭐 이런식으로 enum 중괄호 안에 케이스들을 써주면 된다.
사용할 때는 C와 다르게, 얘네들은 기본적으로 숫자가 붙지 않기 때문에 이름을 불러준다.
그리고 각 Enum 정의는 완전히 새로운 자료형을 만드는 것이기 때문에 앞에 대문자를 써주는 게 국룰!
enum CompassPoint {
case north
case south
case east
case west
}
//여기서 directionToHead의 타입이 CompassPoint로 되는거임!
var directionToHead = CompassPoint.west
//directionToHead의 타입이 암시적으로 CompassPoint임을 알고 있으므로 .east만 해줘도 알아서 됨!
directionToHead = .east
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문은 알다시피 exhaustive 해야 하기 때문에 모든 케이스에 대한 값이 들어가 있어야 됨.
그래서 north나 south같은거 하나라도 빼먹으면 에러나니까 주의!
모든 케이스에다가 각각 값을 할당하는 게 안될 때는 default를 넣어주면 됨!
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless"
Enum을 배열처럼 써먹고 싶을 때가 있다.
이때 바로 뭘 사용하냐!
이 Enum을 CaseIterable 프로토콜을 따르게 해 주면 된다.
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
이런 식으로 만들어서 allCases 타입 프로퍼티를 사용하면 배열에 사용 가능했던 메소드인 count, map, reduce, joined, isEmpty 등이 사용 가능해진다.
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
이런 식으로 배열을 순회할 때 써먹었던 for-in 구문으로 enum을 순회할 수도 있다.
case에 다른 타입들을 저장할 수도 있다!
이런 값들을 associated value라고 하고, 해당 케이스를 코드에서 값으로 사용할 때마다 달라진다.
예를 들어봅시당.
재고관리를 할 때, 물품마다 두 종료의 바코드로 추적하는데 하나는 0~9까지의 숫자로 이루어진 UPC형식 바코드인데 5자리의 제조사 코드번호랑 5자리의 제품 코드번호로 이뤄져 있음. 스캔이 제대로 됐는지 확인하는 체크번호도 있음. 얘네는 정수 4개의 튜플로 저장함.
다른 하나는 ISO 8859-1어쩌구를 따르는 QR코드인데, 얘네는 문자열로 저장함.
그래서 이걸 enum으로 만들어보면 요래됨!
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
이 코드는 다음과 같이 풀어쓸 수 있음!
(Int, Int, Int, Int) 타입인 associated value 를 가지는 value upc나
String 타입인 associated value 를 가지는 value qrCode를 가지는
Barcode라는 enum을 정의해라!
그러면 새로운 바코드를 다음과 같이 만들 수 있다.
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
//이렇게 같은 제품에 다른 타입을 줄 수도 있음.
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
근데 이 두 개의 값은 공존할 수가 없고, Barcode.upc를 선언하고 또 .qrCode를 선언해버리면 upc에 있던 값은 사라져버림.
이런 식으로 switch문으로 productBarcode의 각 케이스마다 전달된 associated value의 각 값을 이름을 지어서 사용할 수도 있다.
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."
게다가 이 enum 케이스의 associated value들이 모두 변수나 상수로 추출되는 형태라면,
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."
요런 식으로 let이나 var를 앞에 빼내서 한 번만 사용할 수도 있음!
enum의 케이스들을 모두 같은 타입의 값으로 채울 수 있다. 이걸 Raw Value 라고 함.
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
Raw value는 String, Int 등등의 타입으로 선언할 수 있지만,
같은 값이 중복되면 안됨!
Raw value를 넣어놓은 enum은 항상 같은 값(raw value)을 가지고 있지만,
Associated value를 사용한 enum은 선언할 때마다 값이 달라질 수 있음.
Swift가 enum 값을 알아서 채워줄 때도 있움.
Int나 String을 넣을 enum일 때 한정인데,
Int면 각 케이스의 앞전 값+1의 값을 가지고,
String이면 각 케이스의 이름을 그대로 rawValue로 가짐!
예제를 봅시다.
// Int일 때 첫번째 케이스 선언 안해두면 0의 값을 가짐.
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
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
이런 식으로! 이건 쉽구만
enum을 raw value로 함께 정의할 때, enum은 자동적으로 raw value의 타입의 값을 가지고, enum 케이스나 nil을 반환해주는 initializer를 받게 된다.
이 initializer로 enum의 새로운 인스턴스를 만들 수 있다.
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
Uranus를 이것의 raw value 7로 찾는 코드이다.
만약에 이 enum에 존재하지 않는 raw value를 찾으려 하면 nil을 반환해야 하므로,
이 possiblePlanet의 자료형은 Planet? 으로 옵셔널이 돼야 한다.
Raw value initializer는 failable initializer이다.
존재하지 않는 raw value를 찾는 코드는 다음과 같다. 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"
다른 enum의 인스턴스를 associated value로 가진 케이스를 하나 이상 갖고 있는 enum을 Recursive Enum이라고 한다.
recursive이라는 것을 알려주기 위해 case 앞에다 indirect 키워드를 넣어줌!
예제를 바로 보자.
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
만약에 모든 케이스가 recursive라면 애초에 enum앞에 indirect를 붙이면 된다.
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
이 Enum은 3가지의 수학적 표현을 넣을 수가 있는데,
이 중 Addition과 Multiplication 케이스는 또다른 수학적 표현을 Associated Value로 가진다.
이 Associated Value로 표현식을 중첩시킬 수 있음!
예를 들어, ( 5 + 4 ) * 2 라는 표현식이 있을 때,
왼쪽 식은 괄호로 묶여 있으므로 nested임. 따라서 이걸 저장할 Enum도 nested여야 하고, 즉 recursive enum을 써야 한다는 것!
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
이런 식으로 쓸 수 있다.