반응형
야곰님의 스위프트 프로그래밍 책으로 공부한 내용을 잊어버리지 않게 간단하게 정리한 글입니다.
관련 포스트
[Swift] 스위프트 프로그래밍 - Part1 - 1 : 스위프트? / 스위프트 장점 / 특징 / 명명 규칙 / 콘솔 로그 / 주석
[Swift] 스위프트 프로그래밍 - Part1 - 2 : 변수 / 상수 / 데이터 타입 기본 (Int, Bool, Float, Character, String, Any, AnyObject, nil)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 1 : 데이터 타입 고급 (Tuple, Array, Dictionary, Set)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 2 : 데이터 타입 고급(열거형 enum)
책 범위 : Part 1. 스위프트 기초 (4. 데이터 타입 고급 - 4.5 열거형)
열거형
- 연관된 항목들을 묶어서 표현할 수 있는 타입
- 배열, 딕셔너리와는 다르게 프로그래머가 정의해준 항목 값 이외에는 추가/수정이 불가 -> 정해진 값만 속할 수 있음
- 각 항목은 값을 가질 수도, 가지지 않을 수 도 있다.
- 값을 가질 경우 각 항목의 값은 원시 값(정수, 실수, 문자 등)의 형태로 실제 값을 가질 수도 있고, 연관 값을 사용해 다른 언어에서 공용체라고 불리는 값의 묶음도 구현할 수 있음
열거형을 유용하게 사용할 수 있는 경우
- 제한된 선택지를 주고 싶을 때
- 정해진 값 이외에는 입력받고 싶지 않을 때
- 예상된 입력 값이 한정되어 있을 때
1. 기본 열거형
- enum 키워드로 선언
- 각 항목은 그 자체가 고유의 값
- 항목이 여러가지일 경우 한 줄에 나열해 표현 가능
// 열거형의 선언
enum School {
case elementry
case middle
case high
case university
}
enum School2 {
case elementry, middle, high, university // 위의 표현 한 줄로
}
// School 열거형 변수의 생성 및 값 변경
var highestEducationLevel: School = School.university
var highestEducationLevel2: School = .university // 위와 같은 표현
highestEducationLevel = .high // 같은 타입인 School의 내부 항목으로만 값 변경 가능
2. 원시 값
- 열거형의 각 항목은 자체로도 하나의 값이지만, 항목의 원시 값(특정 타입으로 지정된 값)도 가질 수 있음
- 열거형 이름 오른쪽에 타입을 명시해주면 됨
- 원시 값을 사용하고 싶다면 rawValue 프로퍼티를 통해 값을 가져올 수 있음
// 열거형의 원시 값 지정과 사용
enum School: String {
case primary = "유치원"
case elementry = "초등학교"
case middle = "중학교"
case high = "고등학교"
case university = "대학교"
}
print(School.elementry.rawValue) // 초등학교
print(School.primary.rawValue) // 유치원
var highestEducationLevel: School = .university
print("\(highestEducationLevel.rawValue)를 졸업했습니다.") // 대학교를 졸업했습니다.
enum weekDays: Character {
case mon = "월", tue = "화", wed = "수", thu = "목", fri = "금", sat = "토", sun = "일"
}
let today:weekDays = weekDays.thu
print("오늘은 \(today.rawValue)요일 입니다.") // 오늘은 목요일 입니다.
- 일부 항목에만 원시 값을 주는 것도 가능
- 문자열의 형식의 원시 값을 지정한다면 각 항목 이름을 그대로 원시 값으로 갖게 됨
- 정수 타입이라면 첫 항목을 기준으로 0부터 1씩 늘어난 값을 갖게 됨
- 열거형의 원시 값 정보를 안다면 원시 값을 통해 열거형 변수, 상수를 생성해줄 수 있음
- 올바르지 않은 원시 값을 통해 생성 시 nil 반환 (실패 가능한 이니셜 라이저 (책 11.1.7) 기능)
// 열거형의 원시 값 일부 지정 및 자동 처리
enum School: String {
case primary
case elementry = "초등학교"
case middle = "중학교"
case high = "고등학교"
case university
}
var highestEducationLevel: School = .university
print("\(highestEducationLevel.rawValue)를 졸업했습니다.") //university를 졸업했습니다.
// 원시 값을 통한 열거형 초기화
let middle = School(rawValue: "중학교")
print(middle!) // middle
let university = School(rawValue: "대학교")
print(university) // nil
////
enum Numbers: Int {
case zero
case one
case two
case ten = 10
}
print("\(Numbers.zero.rawValue), \(Numbers.one.rawValue), \(Numbers.two.rawValue), \(Numbers.ten.rawValue)") // 0, 1, 2, 10
// 원시 값을 통한 열거형 초기화
let one = Numbers(rawValue: 1)
print(one!) // one
let three = Numbers(rawValue: 3)
print(three) // nil
3. 연관 값
- 열거형의 각 항목이 연관 값을 가지게 되면, 기존 프로그래밍 언어의 공용체 형태를 띨 수도 있다.
- 열거형 내의 항목(case)이 자신과 연관된 값을 가질 수 있다.
- 연관 값은 각 항목 옆에 소괄호로 묶어 표현
- 다른 항목이 연관 값을 갖는다고 모든 항목이 연관 값을 가질 필요는 없다.
- 연관값을 확인하고 코드를 실행할 땐 주로 switch 문을 사용한다.
enum pizzaDough {
case cheese, thin, origianl
}
enum pizzaTopping {
case pepperoni, cheese, bacon
}
enum MainDish {
case pasta(sauce: String, topping: String)
case chicken(sauce: Bool)
case rice
case pizza(dough: pizzaDough, topping: pizzaTopping) // 피자의 도우, 토핑 등을 특정 메뉴로 한정 지을려면, 연관값을 열거형으로
}
var dinner = MainDish.pasta(sauce: "tomato", topping: "bacon")
dinner = .chicken(sauce: true)
dinner = .rice
dinner = .pizza(dough: pizzaDough.cheese, topping: pizzaTopping.pepperoni)
// switch를 이용한 연관값의 다양한 표현
switch dinner {
case .chicken(sauce: true):
print("1. Today's dinner menu is chicken with sauce")
case .rice:
print("2. Today's dinner menu is rice")
case .pasta(sauce: "tomato", topping: "bacon"):
print("3. Today's dinner menu is cream bacon pasta")
case .pasta(sauce: "tomato", _): // 와일드카드 패턴 사용 가능
print("4. Today's dinner menu is tomato pasta")
case .pasta: // 연관값 생략 가능
print("5. Today's dinner menu is pasta")
case .pizza(let dough, let topping): // 블록 내부에서 연관값을 사용할 땐 상수로 바인딩, 값을 변경할 때는 var 로 변경 가능
print("6. Today's dinner menu is \(dough) \(topping) pizza")
case let .pizza(dough, topping): // 모든 연관값을 동일한 형태로 바인딩한다면, let 키워드를 열거형 케이스 앞에 표기하는 것도 가능
print("7. Today's dinner menu is \(dough) \(topping) pizza")
default:
print("8. default")
break
}
4. 항목 순회
- 열거형에 포함된 모든 케이스를 알아야 할 때 사용
- 열거형의 이름 뒤에 콜론(:)을 작성하고 한 칸 띄운 뒤 CaseIterable 프로토콜을 채택한다.
- 그러면 열거형에 allCases라는 이름의 타입 프로퍼티를 통해 모든 케이스의 컬렉션을 생성해준다.
- 원시 값을 갖는 열거형일 경우, 원시 값의 타입 다음에 쉼표(,)를 쓰고 띄어쓰기를 한 후 CaseIterable 프로토콜을 채택하면 된다.
// CaseIterable 프로토콜을 활용한 열거형의 항목 순회
enum School: CaseIterable {
case elementry
case middle
case high
case university
}
let allCases: [School] = School.allCases
print(allCases) //[School.elementry, School.middle, School.high, School.university]
// 원시값을 갖는 열거형의 항목 순회
enum School2: String, CaseIterable {
case elementry = "초등학교"
case middle = "중학교"
case high = "고등학교"
case university = "대학교"
}
let allCases2: [School2] = School2.allCases
print(allCases2) //[School.elementry, School.middle, School.high, School.university]
* 복잡한 열거형에서의 CaseIterable 프로토콜 사용에 대한 자세한 내용은 더보기를 클릭해주세요!
더보기
단순한 열거형에는 CaseIterable 프로토콜을 채택해 주는 것만으로 allCases 프로퍼티를 사용할 수 있지만
복잡해지는 열거형은 그렇지 않을 수도 있다.
예를 들어 첫 번째는 플랫폼별로 사용 조건을 추가하는 경우이다.
available 속성을 통해 특정 케이스를 플랫폼에 따라 사용할 수 있거나 없는 경우가 생긴다면 직접 allCases 프로퍼티를 구현해주어야 한다.
// available 속성을 갖는 열거형의 항목 순회
enum School: String, CaseIterable {
case elementry = "초등학교"
case middle = "중학교"
case high = "고등학교"
case university = "대학교"
@available(iOS, obsoleted: 12.0)
case graduate = "대학원"
static var allCases: [School] {
let all: [School] = [.elementry,
.middle,
.high,
.university]
#if os(iOS)
return all
#else
return all + [.graduate]
#endif
}
}
let allCases: [School] = School.allCases
print(allCases) // 실행 환경에 따라 다른 결과 출력
두 번째로는 열거형의 케이스가 연관 값을 갖는 경우이다.
map, reduce 등의 메서드는 15장에 나올 예정
// 연관 값을 갖는 열거형의 항목 순회
enum PastaTaste: CaseIterable {
case tomato, cream
}
enum PizzaDough: CaseIterable {
case cheese, thin, original
}
enum PizzaTopping: CaseIterable {
case pepperoni, cheese, bacon
}
enum MainDish: CaseIterable {
case pasta(taste: PastaTaste)
case pizza(dough: PizzaDough, topping: PizzaTopping)
case chicken(sauce: Bool)
case rice
static var allCases: [MainDish] {
return PastaTaste.allCases.map(MainDish.pasta)
+ PizzaDough.allCases.reduce([]) { (result, dough) -> [MainDish] in
result + PizzaTopping.allCases.map { (topping) -> MainDish in
MainDish.pizza(dough: dough, topping: topping)
}
}
+ [true, false].map(MainDish.chicken)
+ [MainDish.rice]
}
}
print(MainDish.allCases.count) // 14
print(MainDish.allCases) // 모든 경우의 연관 값을 갖는 케이스 컬렉션
5. 순환 열거형
- 열거형 항목의 연관 값이 열거형 자신의 값이고자 할 때 사용
- indirect 키워드 사용
- 특정 항목에만 한정하고 싶다면 case 키워드 앞에 indirect, 열거형 전체에 적용하고 싶다면 enum 키워드 앞에 indirect 키워드를 붙임
// 특정 항목에 순환 열거형 항목 명시
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 열거형 전체에 순환 열거형 명시
indirect enum ArithmeticExpression2 {
case number(Int)
case addition(ArithmeticExpression2, ArithmeticExpression2)
case multiplication(ArithmeticExpression2, ArithmeticExpression2)
}
열거형에는 정수를 연관 값으로 갖는 number라는 항목이 있고,
덧셈을 위한 addition 항목
곱셈을 위한 multiplication 항목이 있다.
아래는 ArithmeticExpression 열거형을 사용해 (5 + 4) * 2 연산을 구하는 예제이다.
evaluate는 ArithmeticExpression 열거형의 계산을 도와주는 순환 함수이다.
// 순환 열거형 사용
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let final = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
let result: Int = evaluate(final)
print("(5 + 4) * 2 = \(result)") // 18
6. 비교 가능한 열거형
Comparable 프로토콜을 준수하는 연관 값만 갖거나 연관 값이 없는 열거형은 Comparable 프로토콜을 채택하면 각 케이스를 비교할 수 있다.
앞에 위치한 케이스가 더 작은 값이 된다.
//비교 가능한 열거형의 사용
enum Condition: Comparable {
case terrible
case bad
case good
case great
}
let myCondition: Condition = Condition.great
let yourCondition: Condition = Condition.bad
if(myCondition >= yourCondition) {
print("I'm in better condition")
} else {
print("You are in better condition")
}
////
enum Device: Comparable {
case iPhone(version: String)
case iPad(version: String)
case macBock
case iMac
}
var devices: [Device] = []
devices.append(Device.iMac)
devices.append(Device.iPhone(version: "14.3"))
devices.append(Device.iPhone(version: "6.1"))
devices.append(Device.iPad(version: "10.3"))
devices.append(Device.macBock)
let sortedDevices: [Device] = devices.sorted()
print(sortedDevices) // Device.iPhone(version: "14.3"), Device.iPhone(version: "6.1"), Device.iPad(version: "10.3"), Device.macBock, Device.iMac]
참고
아래를 참고해 정리한 내용입니다.
스위프트 프로그래밍 3판(야곰) - 한빛미디어
반응형
'iOS' 카테고리의 다른 글
[Swift] 스위프트 프로그래밍 - Part1 - 6 : 흐름 제어 (446) | 2021.04.07 |
---|---|
[Swift] 스위프트 프로그래밍 - Part1 - 5 : 연산자 (0) | 2021.02.16 |
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 1 : 데이터 타입 고급 (Tuple, Array, Dictionary, Set) (0) | 2020.12.21 |
[iOS - MongoDB Realm] 4-1. iOS SDK - Install Realm for iOS (0) | 2020.12.15 |
[iOS - MongoDB Realm] 4. iOS SDK (0) | 2020.12.13 |
댓글