열거형(Enumeration)은 연관된 값들의 집합을 공통된 타입으로 정의하는 것입니다. 열거형은 type-safe 하게 코딩할 수 있는 방법이죠. 주로 요일과 같이 한정된 경우의 수를 정의할 때 사용합니다.
기본적인 문법은 아래와 같습니다.
enum Name {
case firstCase
case secondCase
}
열거형의 이름은 주로 단수형을 사용합니다. C나 Objective-C와는 다르게 각 case 에 기본적인 정수값(0, 1, 2 ...)이 할당되지는 않습니다.
Raw Values
열거형의 case 는 모두 독립적인 값이지만 내부에 또 다른 값을 저장할 수 있습니다. 이것을 원시값(raw value)이라고 합니다. 열거형을 정의할 때 원시값 저장은 필수사항이 아닙니다.
직접 만든 열거형에 원시값을 저장하는 경우는 상대적으로 적습니다. 그러나 개발할 때 주로 사용하는 프레임워크에는 정수형 원시값이 저장된 열거형을 사용하는 경우가 상당히 많습니다.
문법을 한 번 살펴보겠습니다.
enum Name: RawValueType {
case caseName = value
}
먼저 열거형 이름뒤에 원시값의 타입을 지정합니다. 그리고 각 case 마다 원시값을 할당할 수 있습니다. 원시값 타입으로 올 수 있는 것은 String, Character, Number Type 으로 제한됩니다.
선언시점에 저장한 원시값은 나중에 바꿀 수 없습니다. 원시값을 저장하는 부분은 생략도 가능합니다. 생략할 경우, 자료형 마다 각각 원시값 규칙이 존재합니다.
Number Type
enum CompassPoint: Int {
case north
case south
case east
case west
}
원시값 할당을 생략하면 0부터 1씩 증가하며 차례대로 할당됩니다.
CompassPoint.north.rawValue // 0
CompassPoint.south.rawValue // 1
CompassPoint.east.rawValue // 2
CompassPoint.west.rawValue // 3
만약 원시값을 직접 할당하면 어떤 값이 저장되는지 보겠습니다.
enum CompassPoint: Int {
case north
case south
case east = 100
case west
}
.east 에만 100을 할당해 보았습니다. 그 결과,
CompassPoint.north.rawValue // 0
CompassPoint.south.rawValue // 1
CompassPoint.east.rawValue // 100
CompassPoint.west.rawValue // 101
증가될 때의 기준값은 이전 case 이므로 100에서 1증가한 101이 .west 에 저장됩니다.
이번에는 특별한 생성자를 알아보겠습니다.
CompassPoint(rawValue: 0) // north
// 동일한 rawValue를 가진 north가 생성됨
CompassPoint(rawValue: 200) // nil
// 이런 경우에는 nil을 리턴함
// nil을 리턴할 수 있기 때문에 생성자의 리턴형은 옵셔널(CompassPoint?)임
rawValue를 인자로 받는 생성자를 사용하게 되면 해당 원시값과 매칭된 case가 생성됩니다. 위의 경우네는 0과 매칭되는 .north 가 생성됩니다. 만약 매칭되는 case가 없다면 nil을 반환합니다. nil을 리턴할 수 있기 때문에 생성자의 리턴형은 옵셔널(CompassPoint?)임을 알 수 있습니다.
String Type
7개로 한정되는 요일을 열거형으로 만들어 보겠습니다.
enum Weekday: String {
case sunday
case monday
case tuesday
case wednesday
case thursday
case friday
case saturday
}
Weekday.sunday.rawValue // "sunday"
원시값의 자료형을 문자열로 선언하고 원시값 할당을 생략하면 case 이름과 동일한 문자열이 원시값으로 저장됩니다. 당연히 원시값을 직접 할당할 수도 있습니다.
Character Type
Character의 경우에는 항상 원시값을 직접 할당해주어야 합니다.
enum ASCII: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
원시값을 직접 할당해주지 않는다면 컴파일 에러가 발생합니다.
Associated Values
열거형 case에 원시값(raw value)을 저장하는 대신에 연관된 값을 저장할 수도 있습니다. 연관값(associated value)은 원시값의 한계를 해결할 수 있습니다.
원시값의 한계
- 모든 케이스가 동일한 형식을 사용해야함
- 케이스당 값을 하나밖에 저장할 수 없음
- 원시값 문자열에 숫자가 포함되어 있을 경우 숫자만 사용하려면 따로 추출해야하는 번거로움이 있음
연관값의 문법을 먼저 알아보도록 하겠습니다.
enum Name {
case caseName(Type)
case caseName(Type, Type, ...)
}
원시값의 형식은 열거형 이름 뒤에 선언하지만 연관값은 케이스 이름뒤에 선언합니다. 선언 시점이 아닌 새로운 열거형을 생성할 때 값을 저장합니다. 보시다시피 튜플(Tuple)을 사용하여 하나의 케이스에 서로 다른 연관값들을 저장할 수 있음을 알 수 있습니다.
예시를 통해 연관값 활용방법을 알아보겠습니다.
enum AppleDevice: String {
case iPhone = "X, 256GB"
case iMac = "27, Pro, 300만원"
case macBook = "Air, 1kg, 150만원"
}
애플 기기에 대한 정보를 원시값을 이용하여 문자열로 저장하고 있습니다. 각 기기마다 저장할 값들이 다르고 단순히 콤마(,)로 구분되어 있어 해당 값을 추출할 때 번거로워 보입니다. 이를 연관값을 활용하여 바꿔보겠습니다.
enum AppleDevice {
case iPhone(model: String, storage: Int) // named tuple
case iMac(size: Int, model: String, price: Int)
case macBook(String, Int, Int) // unnamed tuple
}
상황에 알맞게 named 혹은 unnamed 튜플을 사용할 수 있습니다. 연관값을 확인한 후에 코드를 실행할 때는 주로 switch 문을 사용합니다.
var gift = AppleDevice.iPhone(model: "X", storage: 256)
switch gift {
case .iPhone(model: "X", storage: 256):
print("iPhone X and 256GB")
case .iPhone(model: "X", _)
// 와일드카드 패턴 사용 가능
print("iPhone X")
case .iPhone:
// 연관값 생략 가능
print("iPhone")
case .iPhone(let model, let storage):
// 블록 내부에서 연관값을 사용할 땐 상수로 바인딩
// 값을 변경할 때는 var 로 변경가능
print("iPhone \(model) and \(storage)GB")
case let .iMac(size, model, price):
// 모든 연관값을 동일한 형태로 바인딩한다면
// let 키워드를 열거형 케이스 앞에 표기하는 것도 가능
print("iMac \(size), \(model), \(price)")
}
새로운 케이스를 할당할 경우 모두 새로운 값으로 교체됩니다.
gift = .macBook("Air", 1, 150)
if case let .macBook(model, weight, price) = gift {
print("maccBook \(model), \(weight)kg, \(price)")
}
마지막에 사용한 if-case 문은 다음 포스팅에서 자세히 알아보도록 하겠습니다.
정리
- 열거형에 원시값 혹은 연관값을 저장하는 방법을 알아보았습니다.
- String, Character, Number Type 만 원시값으로 저장할 수 있습니다.
- 각 타입마다 원시값 할당에 대한 규칙이 존재합니다.
- 원시값의 한계를 연관값을 사용하여 해결하였습니다.
- 원시값을 확인할 때는 주로 switch 문을 활용합니다.
'Swift' 카테고리의 다른 글
[Swift] Namespace 네임스페이스 란? (Struct, Enum 활용) (0) | 2019.12.15 |
---|---|
[Swift] Enum 열거형 정복하기 2편 (Enum Case Pattern) (0) | 2019.12.15 |
[Swift] Type Casting (is, as, as?, as!) 타입캐스팅 완벽 정리 (0) | 2019.12.15 |
[Swift] Escaping Closure 탈출 하는 클로저!? (0) | 2019.12.15 |
[Swift] Capturing Values 값을 캡쳐한다는 것 (0) | 2019.12.15 |