Swift
Swift) Enumeration
kangwook
2022. 10. 4. 23:49
Enum이란
컴퓨터 프로그래밍에서 열거형은 요소, 멤버라 불리는 명멷뇌 값의 집합을 위루는 자료형.
열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자.
쉽게 말하면 상수 역할의 값들을 보기 쉽게 나열해 놓은 것
Raw Values(원시 값)
enum의 case는 모두 독립적인 값이지만 내부에 또 다른 값을 저장할 수 있음 → raw value
enum Name : RawValueType {
case caseName = value
}
- 원시 값 타입으로 올 수 있는 것은 String, Character, Number Type
- 선언 지점에 저장한 원시 값은 나중에 바꿀 수 없음
- 원시 값을 저장하는 부분은 생략도 가능, 각각의 자료형마다 규칙이 존재
enum CompassPoint: Int {
case north
case south
case east
case west
}
CompassPoint.north.rawValue // 0
CompassPoint.south.rawValue // 1
CompassPoint.east.rawValue // 2
CompassPoint.west.rawValue // 3
- 또한 원시값을 통한 case매칭으로 enum을 생성할 수 있음
CompassPoint(rawValue: 0) // north
// 동일한 rawValue를 가진 north가 생성
CompassPoint(rawValue: 200) // nil
// 없는 경우에는 nil을 리턴
String Type
enum Weekday: String {
case sunday
case monday
case tuesday
case wednesday
case thursday
case friday
case saturday
}
Weekday.sunday.rawValue // "sunday"
- String으로 선언할 경우 원시 값 할당을 생략하면 case이름과 동일한 문자열이 원시값으로 저장
- 직접 할당할 수도 있음
Character Type
enum ASCII: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
- Character 같은 경우 원시 값을 직접 할당해주지 않으면 컴파일 에러 발생
Associated Values(연관 값)
원시값의 한계
- 모든 케이스가 동일한 형식을 사용해야 함
- 케이스당 값을 하나밖에 저장할 수 없음
- 원시값 문자열에 숫자가 포함되어 있을 경우 숫자만 사용하려면 따로 추출해야하는 번거로움이 있음
enum Name {
case caseName(Type)
case caseName(Type, Type, ...)
}
- 원시 값의 경우 열거형 이름 뒤에 선언하지만, 연관 값은 케이스 이름 뒤에 선언
- 튜플을 사용하여 하나의 케이스에 서로 다른 연관 값들을 저장할 수 있음
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
}
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("macBook \(model), \(weight)kg, \(price)")
}
Enum with Protocol
protocol MenuProtocol {
var menuName: String { get }
mutating func nextMenu() -> Self
}
- 프로퍼티는 enum에서 연산 프로퍼티만 사용할 수 있기 때문에 { get } 만 존재
enum FastFoodMenu: MenuProtocol {
case chicken
case pizza
case hamburger
var menuName: String {
switch self {
case .chicken:
return "치킨"
case .pizza:
return "피자"
case .hamburger:
return "햄버거"
}
}
func nextMenu() -> FastFoodMenu {
switch self {
case .chicken:
return .pizza
case .pizza:
return .hamburger
case .hamburger
return .chicken
}
}
}
enum NoodleMenu: MenuProtocol {
case ramen
case pasta
case udon
var menuName: String {
switch self {
case .ramen:
return "라멘"
case .pasta:
return "파스타"
case .udon:
return "우동"
}
}
func nextMenu() -> NoodleMenu {
switch self {
case .ramen:
return .pasta
case .pasta:
return .udon
case .udon
return .ramen
}
}
}
enum BeverageMenu: MenuProtocol {
case coke
case sprite
case water
var menuName: String {
switch self {
case .coke:
return "콜라"
case .sprite:
return "사이다"
case .water:
return "물"
}
}
func nextMenu() -> BeverageMenu {
switch self {
case .coke:
return .sprite
case .sprite:
return .water
case .water:
return .coke
}
}
}
var vendingMachine = BeverageMenu.coke
print(vendingMachine.menuName) // "콜라"
vendingMachine = vendingMachine.nextMenu()
print(vendingMachine.menuName) // "사이다"
vendingMachine = vendingMachine.nextMenu()
print(vendingMachine.menuName) // "물"
extension을 사용해서 케이스와 메서드, 프로퍼티를 분리하여 가독성을 높일 수 있음
enum FastFoodMenu: MenuProtocol {
case chicken
case pizza
case hamburger
}
extension FastFoodMenu: MenuProtocol {
var menuName: String {
switch self {
case .chicken:
return "치킨"
case .pizza:
return "피자"
case .hamburger:
return "햄버거"
}
}
func nextMenu() -> FastFoodMenu {
switch self {
case .chicken:
return .pizza
case .pizza:
return .hamburger
case .hamburger
return .chicken
}
}
}
Protocol자체를 extension과 함께 사용함으로써 가독성을 더욱 높일 수 있음
protocol MenuProtocol {
var menuName: String { get }
func nextMenu() -> Self
}
extension MenuProtocol {
var menuName: String {
return String(describing: self)
}
}
Generic Enumeration(제네릭 열거형)
제네릭으로 구현한 열거형의 대표적인 예는 Optional
Apple Documentation : Optional
Apple Developer Documentation
developer.apple.com
enum Optional<Wrapped> {
case none // 값이 없을 경우
case some(Wrapped) // 값이 있을 경우
}
let someValue = Optional<String>.some("some")
let nilValue = Optional<String>.none
print(someValue) // Optional("some")
print(nilValue) // nil
Recursive / Indirect Enumeration(재귀적/간접적 열거형)
재귀 / 간접 타입을 사용하면 열거형의 각 항목의 연관 값으로 열거형 타입을 지정 가능
자기 자신을 참조하는 열거형은 무한한 크기를 가질 수 있기 때문에 해당 타입이 재귀 / 간접 타입이라는 것을 명시해주어야 함
indirect enum LinkedListItem<T> {
case end(value: T)
case node(value: T, next: LinkedListItem)
}
enum LinkedListItem<T> {
case end(value: T)
indirect case node(value: T, next: LinkedListItem)
}
let cNode = LinkedListItem.end(value: "c")
let bNode = LinkedListItem.node(value: "b", next: cNode)
let aNode = LinkedListItem.node(value: "a", next: aNode)
var current = aNode
linkedListLoop: while true {
switch current {
case let .end(value):
print(value)
break linkedListLoop
case let .node(value, next):
print(value)
current = next
}
}
/**
a
b
c
**/
Custom Constructor(커스텀 생성자)
API를 통해 String, Int값을 받아온 후 열거형으로 변환하여 사용하려고 할 때 사용
구조체나 클래스의 생성자를 만드는 방법과 동일 → init
enum ErrorCode {
case Code400
case Code404
case Code500
case CodeNil
init(errorCode: Int) {
switch errorCode {
case 400:
self = .Code400
case 404:
self = .Code404
case 500:
self = .Code500
default:
self = .CodeNil
}
}
var errorMessage: String {
switch self {
case .Code400:
return "서버 요청 실패"
case .Code404:
return "페이지가 존재하지 않음"
case .Code500:
return "서버 응답 없음"
case .CodeNil:
return "알 수 없는 에러"
}
}
}
let errorCode = ErrorCode.init(errorCode: 404)
print(errorCode.errorMessage)
/** 페이지가 존재하지 않음 **/
let errorCode2 = ErrorCode.init(errorCode: 700)
print(errorCode2.errorMessage)
/** 알 수 없는 에러 **/
- 참조되어 있는 블로그들과 애플 공식 문서를 통해 공부했습니다.
참조